Skip to content

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:

Four constraints you want to hold, three homes each: a rule (the model is told), a permission (the harness forbids a class of action), a hook (your code runs on the rail, every time). All four start in the rules file. Re-home them - then move the clock, because what holds while you watch isn’t what holds at 2am.

situation
  • the fragile modulea preference · low stakes

    The auth module is fragile - prefer minimal, surgical diffs there.

    held - you’re the gate

    “Minimal and surgical” is a judgment call, not a checkable condition. You want the model informed and weighing it - and your diff review is the backstop for the times it doesn’t.

  • the secrets walla never · high stakes

    The agent must never read secrets/.

    held - you’re the gate

    An instruction the model weighs - so “never” actually means “unless a debugging trail leads there after the line compacted out.” Walls that matter don’t get to be suggestions.

  • the tested-commit gatea condition on content · high stakes

    No commit that touches money code unless the money tests pass.

    held - you’re the gate

    It works all morning, which is what makes it dangerous. The line compacts out at hour three, and the untested commit lands at 2am with nobody to catch it.

  • the every-time formatteran every-time · low stakes

    Every file the agent writes gets formatted - every time, not most times.

    held - you’re the gate

    The model formats when it remembers, and “every time” done by memory is “most times.” No single miss hurts; the drift and the diff noise pile up.

holding4 of 4shaky0silently broken0

All four look fine - and that’s the trap state. While you watch, an instruction is indistinguishable from a guarantee, because you’re the enforcement. The file didn’t hold the line; you did. Move the clock.

Each tool wears its own names - permissions may be an approvals-and-sandbox dial, a hook a plugin on lifecycle events - but the ladder is the same: guarantee strength is set by what sits in the loop - the model’s memory, a harness wall, or your code.

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.