When a hook beats judgment or a permission
You’ve now governed Codex three different ways across this course, and the ledger gate you just built sits apart from the other two in a way worth making explicit - because reaching for the wrong one leaves you with a guarantee weaker than you think you have. Lay them side by side:
- A rule (a line in
AGENTS.md) is context. It tells the model what’s true and what you want, and the model weighs it when it decides. It lives in the context window, which means it can be compacted out, buried under a long session, or simply not weighted heavily enough on a given turn. You’re trusting the model to remember and to care. - The approvals and sandbox dials are model-mediated and environmental enforcement. They’re stronger than a rule - the sandbox is a genuine wall, and the agent can’t write outside its writable roots or hit the network when you’ve turned it off. But they govern capability: which tools the agent may invoke, where it may write, whether it may reach the network. They answer “may the agent do this kind of thing?” - not “is the content of this specific command acceptable?”
- A hook is deterministic, model-independent code on a supported lifecycle path. It doesn’t advise the model and doesn’t sit in the context window at all. It can inspect the actual payload and make a deterministic decision, subject to hook trust and coverage rules. The model’s reasoning, its mood, its remaining context budget - none of it is in the hook itself.
The sharpest way to feel the difference: a rule routes through the model’s judgment, and the sandbox routes through a capability check. A hook routes through neither. That’s why the ledger guarantee had to be a hook. “Never write to ledger_prod” isn’t a fact to remember (a rule), and it isn’t a capability to revoke (running psql is a thing you obviously do let Codex do). It’s a condition on the content of a specific command, checked deterministically at the instant that command is attempted. Only a hook expresses that.
Stress-test the ladder yourself - here “permission” is Codex’s approvals-and-sandbox dial, and the clock is the part to play with, because what holds while you watch isn’t what holds in an unattended run:
A decision rule
Section titled “A decision rule”So which do you reach for? The test is about what kind of guarantee the situation demands:
- State a preference or a fact the agent should weigh - “money is integer cents, never float,” “the categoriser is fragile, be careful.” That’s a rule. You want the model informed, and you accept that it’s the model deciding.
- Constrain what the agent can do or where it can reach - read-only on this run, no network, don’t write outside the data dir. That’s the approvals and sandbox model. You want a wall around a capability or a path, with no per-command judgment involved.
- Check a condition at a supported lifecycle point - no write to the production ledger, run the money tests after an edit, or flag a risky command before it runs. That’s a hook. You want a deterministic guardrail that does not depend on the agent remembering the rule; pair it with sandbox, database, CI, or OS controls when the condition is security-critical.
Put plainly: a rule shapes what the agent knows, the sandbox shapes what it can do by default, and a hook checks what happens at a supported lifecycle point. The first two still need a clear approval posture; the hook does not depend on the model remembering the check, but it is not a universal replacement for infrastructure-level enforcement. It can also do the positive version, firing a test suite or a format pass after a matching action, not just blocking.
Why this is the hinge of the whole chapter
Section titled “Why this is the hinge of the whole chapter”That last property - a deterministic check at a supported hook point - is why hooks help when you stop watching Codex work. Sitting at the TUI, you are still part of the approval boundary. The hook’s real value shows up when you’re not there - when the report regenerates on a schedule, when the categoriser runs against new transactions in CI, or when a turn finishes at 2am. In those runs, combine trusted hooks with sandboxing, least-privilege credentials, database permissions, and CI gates rather than treating one hook as the only wall.
Which is the whole reason this chapter came before the next one. You’ve now given budgetcli both halves of what unattended work needs: reach (the rate API over MCP, so a headless run can actually do the conversion) and a gate (the ledger hook and the test-on-edit hook, so it can’t quietly break the money math). With both in place, running Codex without a human watching stops being reckless and starts being the point.
That’s Chapter 9: generate the monthly report with codex exec, wire the categoriser and the tests into CI, and let the whole loop run with nobody in the chair. Everything you built here is what makes that safe.