Skip to content

Hooks, rules, and permissions

Hooks automate lifecycle behavior. Rules, sandboxing, approvals, and permissions determine authority. Use them together, but never treat them as the same boundary.

When to use each control

  • Run formatting after an edit: use a PostToolUse hook or formatter command, supported by a CI formatting check.
  • Block a known command prefix outside the sandbox: use Codex .rules or Claude permissions, supported by a PreToolUse explanation or audit hook.
  • Protect sensitive paths: use filesystem, sandbox, or permission policy, supported by a PreToolUse hook for supported edit paths.
  • Restore context after Codex compaction: use a Codex PostCompact hook, supported by a durable plan and repository docs.
  • Restore context after Claude compaction: use Claude SessionStart with a compact matcher, supported by a durable plan and repository docs.
  • Check completion evidence: use a Stop hook with loop protection, supported by CI and the skill output contract.
  • Add directory-specific context automatically: use SessionStart, a prompt, or scoped instructions, supported by indexed docs.
  • Audit tool or prompt activity: use a lifecycle hook, supported by central logs and a retention policy.
  • Approve consequential actions: use the native approval flow, supported by a narrow PermissionRequest hook only when justified.

Use a hook only when the event itself should cause the behavior. If the model must decide whether a procedure applies, use a skill. If a rule must hold even when hooks are disabled, bypassed, unsupported, or fail, encode it in native permissions and CI.

OpenAI Codex hooks

Codex loads hooks from hooks.json or inline [hooks] tables next to active config layers. The practical locations are user or repo hooks.json and config.toml; enabled plugins can also provide hooks. Project hooks require a trusted project, and each non-managed command-hook definition is trusted by content hash. Changed hooks are skipped until reviewed again.

Prefer one representation per layer. If both inline and file hooks exist, Codex merges them and warns. Hooks from different active layers also accumulate rather than replacing one another.

Current documented events include:

  • SessionStart and SubagentStart for startup context;
  • UserPromptSubmit before a user prompt enters the loop;
  • PreToolUse, PermissionRequest, and PostToolUse around supported tools;
  • PreCompact and PostCompact around manual or automatic compaction;
  • SubagentStop and Stop at completion boundaries.

Only command handlers currently execute in Codex. Prompt and agent handlers are parsed but skipped, as are asynchronous command hooks. Default timeout is 600 seconds; shared hooks should set a much smaller intentional timeout.

Example project hook:

{
"hooks": {
"PreToolUse": [
{
"matcher": "^Bash$",
"hooks": [
{
"type": "command",
"command": "\"$(git rev-parse --show-toplevel)/.codex/hooks/policy.py\"",
"timeout": 30,
"statusMessage": "Checking shell command"
}
]
}
]
}
}

Commands run with the session working directory. Resolve repo scripts from the git root because a client may start in a subdirectory.

Codex limitations that affect policy

  • Multiple matching command hooks start concurrently; do not make them race to mutate the same state.
  • PreToolUse currently intercepts supported Bash, apply_patch, and MCP calls, but not every shell or tool path. It is defense in depth, not complete enforcement.
  • PostToolUse cannot undo an action that already ran; it can replace feedback or guide remediation.
  • Matchers filter tool names for tool events, not arbitrary arguments. Parse and validate structured input inside the hook.
  • Plugin installation does not confer hook trust.
  • Stop hooks need an explicit progress/loop guard.

Codex command rules

Put project rules under .codex/rules/*.rules in a trusted repository. A prefix_rule() matches an argument prefix and returns allow, prompt, or forbidden; the strictest matching decision wins. Provide a justification and inline positive/negative cases:

prefix_rule(
pattern = ["gh", "pr", ["view", "list"]],
decision = "prompt",
justification = "Read pull-request data only with approval",
match = ["gh pr view 123", "gh pr list"],
not_match = ["gh issue view 123"],
)

Test it directly:

Terminal window
codex execpolicy check --pretty \
--rules .codex/rules/default.rules \
-- gh pr view 123

Rules control commands requested outside the sandbox and are currently experimental. They do not replace sandbox, network, MCP, or connector review. Managed requirements.toml can enforce restrictive rules and managed-only hooks for organizations.

Anthropic hooks and permissions

Claude Code documents a broader hook handler set: command, prompt, agent, and HTTP. Command hooks remain the production default for deterministic checks; model or remote handlers add cost, latency, and a larger trust boundary.

A Claude hook is an event plus an optional matcher and one or more handlers. Command handlers receive the event JSON on stdin. Exit zero signals success; exit two expresses a blocking decision where the event permits one; another non-zero exit is an execution error. Use structured JSON when changing tool input or returning additional context, and keep diagnostics off JSON stdout.

Event placement is semantic: inspect tool_input.file_path in PreToolUse before protecting an edit, format in PostToolUse after a write, and restore compacted context through SessionStart with the compact matcher. A PermissionRequest hook is not a substitute for pre-use policy because that event is absent in some non-interactive execution.

A minimal project formatter is shaped like this:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/scripts/format-edited-file.sh"
}
]
}
]
}
}

The script reads the edited path from stdin JSON; the filename is not supplied as an implicit positional shell argument.

Claude matching hooks run in parallel. PostToolUse also cannot undo an action. Best-effort argument filters may fail open, and PermissionRequest is not emitted in every non-interactive environment. Use Claude permission rules for hard allow/deny policy and a pre-use event when automated enforcement must work without an interactive approval event.

Hook implementation contract

Every shared hook should document:

  • owner and purpose;
  • client, event, and exact matcher;
  • input JSON schema and example fixtures;
  • stdout, stderr, structured JSON, and exit-code behavior;
  • timeout, idempotency, concurrency, and retry expectations;
  • fail-open or fail-closed behavior;
  • supported and explicitly unsupported tool paths;
  • secrets, network, filesystem, and logging behavior;
  • bypass, disable, recovery, and audit procedures.

Test the script directly with fixture JSON before testing it through the client. Keep stdout clean when structured JSON is expected and put diagnostics on stderr. Treat input fields as untrusted and avoid shell interpolation of raw values.

Sources: OpenAI hooks, OpenAI advanced configuration, OpenAI rules, and Anthropic hooks.