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
PostToolUsehook or formatter command, supported by a CI formatting check. - Block a known command prefix outside the sandbox: use Codex
.rulesor Claude permissions, supported by aPreToolUseexplanation or audit hook. - Protect sensitive paths: use filesystem, sandbox, or permission policy,
supported by a
PreToolUsehook for supported edit paths. - Restore context after Codex compaction: use a Codex
PostCompacthook, supported by a durable plan and repository docs. - Restore context after Claude compaction: use Claude
SessionStartwith acompactmatcher, supported by a durable plan and repository docs. - Check completion evidence: use a
Stophook 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
PermissionRequesthook 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:
SessionStartandSubagentStartfor startup context;UserPromptSubmitbefore a user prompt enters the loop;PreToolUse,PermissionRequest, andPostToolUsearound supported tools;PreCompactandPostCompactaround manual or automatic compaction;SubagentStopandStopat 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.
PreToolUsecurrently intercepts supported Bash,apply_patch, and MCP calls, but not every shell or tool path. It is defense in depth, not complete enforcement.PostToolUsecannot 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:
codex execpolicy check --pretty \ --rules .codex/rules/default.rules \ -- gh pr view 123Rules 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.