Codex vs Claude

Thirty-Three Hook Events or Three Approval Policies: Matching the Instrument to the Rule

David Guzenburg/ / 11 min read

The expressive gap between the two automation models is real. It is also almost entirely ceiling, and you will spend your time on the floor.

hooksautomationpermissionsgovernance

Thirty-three named events against a handful of scopes

The two agents automate themselves at different resolutions, and the gap is wide enough to change what kinds of policy you can express.

Claude Code's hook system fires on named lifecycle events, and the list is long. Per-session: SessionStart, SessionEnd, Setup. Per-turn: UserPromptSubmit, UserPromptExpansion, Stop, StopFailure. Around every tool call: PreToolUse, PermissionRequest, PermissionDenied, PostToolUse, PostToolUseFailure, PostToolBatch. Around delegated work: SubagentStart, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle. Around context and configuration: InstructionsLoaded, ConfigChange, CwdChanged, DirectoryAdded, FileChanged, WorktreeCreate, WorktreeRemove. Around compaction and models: PreCompact, PostCompact, PreModelSwitch, PostModelSwitch. Plus Notification, MessageDisplay, Elicitation and ElicitationResult.

Codex approaches automation from the other end. Its primary levers are coarse and declarative: an approval_policy that takes untrusted, on-request or never, and a sandbox_mode that takes values including workspace-write and danger-full-access, selectable as named profiles. Lifecycle hooks arrived later, configured from a hooks.json or an inline [hooks] block and browsable from /hooks.

The argument here is that the fine-grained model is genuinely more expressive, that almost nobody uses more than a fraction of it, and that the choice between the two is not about which is more powerful but about whether your policy is a rule about a specific tool call or a statement about the whole session. Those need different instruments, and reaching for the wrong one is how teams end up with hook scripts nobody can explain.

What only the fine-grained model can do

There is a class of control that requires an event at the tool boundary, and no amount of session-level configuration substitutes for it.

The canonical case is a deterministic veto on a specific command shape. A PreToolUse hook matched to Bash can inspect the command the model is about to run and return a decision that blocks it, carrying a reason the agent sees. That is not a prompt asking a human, and it is not a sandbox that would have caught it anyway; it is a rule that executes identically every time, on a decision the model would otherwise have made for itself.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

The matcher semantics are worth knowing because they are quietly load-bearing. A matcher of only letters, digits, underscores, hyphens, spaces, commas or pipes is an exact string or a pipe-separated list — Bash, Edit|Write. Anything containing other characters is treated as an unanchored JavaScript regular expression, so mcp__memory__.* matches a family of tools and ^Notebook anchors deliberately. Adding a dot to a matcher you believed was a literal silently converts it into a pattern.

The second thing only the fine-grained model gives you is scope. Hooks can be declared in ~/.claude/settings.json for every project you touch, in a committed .claude/settings.json for one repository, in a gitignored .claude/settings.local.json for you alone, in managed policy settings an administrator controls, in a plugin, in skill frontmatter for the rest of a session, or in subagent frontmatter for the lifetime of that subagent. That layering is the difference between a personal convenience and an organisational control, and it is expressed in where the file lives rather than in what it says.

What the coarse model gets right

Set against thirty-three events, three approval policies and a handful of sandbox profiles look impoverished. They are not, for a reason worth stating plainly: most of what teams actually want from hooks is a statement about the whole session, and a session-level setting says it in one line that a new team member can read.

Consider what people usually configure. Run the formatter after edits. Run the type checker before finishing. Do not touch these paths. Do not reach the network. Ask before anything destructive. Of those five, three are naturally session-wide and two are path rules. None of them requires knowing that PostToolBatch exists.

The coarse model also fails more legibly. A sandbox profile that denies network access denies it to everything, including the case you did not anticipate, and it does so at a boundary below the model. A PreToolUse hook that was supposed to catch destructive commands catches exactly the shapes its author thought of, and the gap between the rule and the intent is invisible until something slips through it.

This comparison has a short shelf life

Comparisons written in the first half of 2026 list Codex as having no hooks at all, and they were right when written. By August 2026 lifecycle hooks are documented as configurable from hooks.json or an inline [hooks] block. The direction of travel is convergence, and any claim in this area — including this article's — should be checked against both vendors' current documentation rather than trusted at second hand.

Two events out of thirty-three

In practice the distribution of hook usage is extremely skewed. PreToolUse and PostToolUse account for nearly everything anybody writes: block the dangerous thing, run the formatter after the edit. A third tier — SessionStart to load context, PreCompact to preserve something before it is summarised away — shows up in perhaps one repository in ten. The rest are almost entirely unused, not because they are badly chosen but because most teams do not have a policy that needs them.

That has a consequence for how you should read the feature comparison. A table showing thirty-three events against a smaller number is measuring ceiling, not floor, and the floor is where you will live. If your policy is two rules, the tool with two hundred events and the tool with six will both express it, and the one with six will express it in fewer lines that more people can read.

The ceiling matters when your policy is not two rules. Auditing every subagent spawn, reacting to a model switch mid-session, capturing state before compaction, refusing a prompt expansion — these are real requirements for teams operating agents at scale, and they need events that exist.

Where the two models genuinely diverge: enforcement point

Underneath the granularity question sits a more consequential one, which is where the enforcement happens.

A hook is application-layer. It runs because the agent's own harness chose to call it, and it constrains what that harness will do. A sandbox profile is enforced below the application, by the operating system: Seatbelt on macOS, Landlock on Linux, restricted tokens and filesystem ACLs on Windows. That distinction is not a matter of taste. An application-layer control is defeated by anything that gets outside the application — a subprocess, a wrapper script, a tool the harness does not route through the hook — and a kernel control is not.

Both products now offer both kinds. The relevant question for a given rule is which layer it belongs at, and the honest answer is usually the lower one. If the rule is "this process must not reach the network", express it as egress policy rather than as a hook that inspects commands for curl, because the hook is a list of spellings and the network policy is a property of the environment.

The failure this produces

Teams write a PreToolUse hook that greps for dangerous command strings, then treat it as a security control. It is a typo guard. It stops the model doing the obvious wrong thing, which is worth having, and it stops nothing that is trying. Anything you would describe to an auditor as a control needs to be enforced below the agent.

Choosing an instrument for a given rule

A rule of thumb that survives both products and most disagreements about them: match the instrument to what the rule is a statement about.

If the rule is a property of the environment — what may be reached, what may be written, what credentials exist — it belongs in the sandbox or the network policy, on either tool, and expressing it as a hook is a downgrade.

If the rule is a property of a specific tool call — this command shape, this path, this MCP server — it belongs in a fine-grained hook, and this is where the thirty-three-event model earns its complexity.

If the rule is a property of the whole session — how much autonomy this run gets, whether it may act unattended — it belongs in an approval policy or a profile, and writing it as a hook means reimplementing a mode that already exists.

If the rule is a property of the work rather than the tooling — this must be reviewed, this must pass tests before merging — it belongs in CI, where it applies to human commits too and cannot be turned off by switching agents.

rule                          instrument (either product)
---------------------------------------------------------
no network from this run       sandbox / egress policy, not a hook
never rm -rf without asking    fine-grained pre-tool hook
unattended overnight run       approval policy / profile
nothing merges unreviewed      CI, not the agent at all

The maintenance cost nobody budgets

Hooks are code that runs on every relevant event, and they inherit every property of code: they break, they get slow, they encode assumptions that stop being true. A hook that shells out on every PostToolUse adds its runtime to every edit, and a formatter invoked that way on a large file is a delay the user experiences as the agent being slow.

They also fail in a direction people do not expect. A hook that errors is usually treated as no decision, which means a control you believe is active can be silently inert because a path changed. If you rely on one, test that it still blocks — deliberately, on a schedule — the same way you would test a backup.

The coarse model has less of this exposure simply because there is less of it. That is not an argument that granularity is bad. It is an argument that every event you hook is a small ongoing obligation, and that a team choosing the more expressive tool should hook the two events it needs rather than the thirty-three it now can.

Where these facts come from

The hook event names, the settings file scopes and the matcher evaluation rules are from Anthropic's Claude Code hooks documentation. The approval_policy and sandbox_mode values, and the hooks.json and inline [hooks] configuration, are from OpenAI's Codex configuration documentation. The observation that Codex had no hook system in mid-2026 is from Firecrawl (June 3, 2026); the kernel-level sandbox mechanisms named there — Seatbelt, Landlock, Windows restricted tokens — are corroborated by the Wikipedia record. The claim that two events account for most real usage is my own observation from repositories I have worked in, not a measured figure.

Takeaway

Claude Code exposes thirty-three named lifecycle events with layered configuration scopes; Codex leads with three approval policies and a few sandbox profiles, and has since added its own lifecycle hooks. The expressive gap is real, but almost all actual usage is two events, so the comparison measures ceiling rather than floor. Choose the instrument by what the rule is a statement about: environment rules belong in the sandbox, tool-call rules in a fine-grained hook, session autonomy in an approval policy, and anything you would call a control belongs below the agent rather than inside it — because a hook is application-layer, and application-layer controls are defeated by anything that gets outside the application.

Keep reading
Codex vs Claude

Approval Fatigue: The Control Degrades Every Time You Use It

Interactive permission prompts spend a consumable resource. Why the count matters more than the wording, why deny lists beat allow lists, and how to make destructive commands break the rhythm instead of matching it.

Codex vs Claude

Lifecycle Hooks or Standard Policy Boundaries?

Hooks are strongest when an organisation needs repository-specific deterministic enforcement or integration. Standard policy boundaries are strongest.

Codex vs Claude

The Hook Lifecycle: Everyone Uses Two Events Out of Thirty

Session start for environment assertions, prompt submission for state that moved, before-compaction for the constraints a long session is about to lose, failure events, subagent boundaries and worktree setup — and why cadence sets your performance budget.

Tooling & Integration

Running Coding Agents in CI: Guardrails for Unattended Work

What changes when no human is watching, which tasks are safe to automate, and the five controls that turn an unattended agent job into a bad pull request rather than a bad deployment.

← One Agent Across App, CLI and IDE—or Terminal First?

All codex vs claude articles  ·  Every article