Claude Code Hooks + Codex Notifications
Use this skill when hook behavior is the main concern: Claude lifecycle hooks, runtime preflight, guardrails, async verification, worktree lifecycle checks, subagent coordination, or Codex notification callbacks.
Claude and Codex are not equivalent here. Claude has a broad, stable hook system (31 events, re-verified against official docs 2026-08-15). Codex has two surfaces: the long-stable notification surface (notify + tui.notifications), and a newer lifecycle-hooks system (hooks.json with events including SessionStart, UserPromptSubmit, SubagentStop, Stop, PreCompact/PostCompact — see developers.openai.com/codex/hooks). The Codex hooks system is less mature than Claude's and has known firing-reliability gaps (e.g. codex#17532: repo-local .codex/config.toml hooks may not fire in interactive sessions). Prefer notify for anything that must be dependable; treat Codex hooks.json as usable-but-verify and confirm it fires on your runtime before relying on it.
Quick Reference
| Need |
Event / Approach |
| enforce pre-tool policy or guardrails |
PreToolUse command hook |
| fast runtime checks at session start |
SessionStart or Setup |
| react to user prompt before Claude sees it |
UserPromptSubmit |
| run checks after edits (non-blocking) |
PostToolUse (async/background) |
| catch tool failures separately |
PostToolUseFailure |
| check after a full batch of parallel tool calls |
PostToolBatch |
| inject context into or coordinate subagents |
SubagentStart / SubagentStop |
| transform what the user sees (not the transcript) |
MessageDisplay |
| persist compact durable state before compaction |
PreCompact |
| react after compaction completes |
PostCompact |
| handle worktree setup and teardown |
WorktreeCreate / WorktreeRemove |
react to cd or watched file changes |
CwdChanged / FileChanged |
| keep agent team from going idle |
TeammateIdle |
| handle API errors at turn end |
StopFailure |
| reload plugin hooks safely |
atomic clear-then-register swap |
| add Codex callback behavior |
notify external program plus tui.notifications |
| enforce budgets, iteration caps, stagnation, kill-switches |
references/budget-and-loop-hooks.md |
When To Use This Skill
Use this skill when the task is:
- building Claude hook automation
- adding command guardrails or approval logic
- wiring verification or audit hooks
- managing hook-scoped repo hygiene
- configuring Codex notifications or callback programs
Route elsewhere when the main concern is:
Capability Boundary
| Capability |
Claude Code |
Codex |
| broad lifecycle hooks |
yes (stable, 31 events) |
yes via hooks.json (newer, fewer events, reliability gaps) |
| command or decision control |
yes |
yes (PreToolUse deny; Stop/SubagentStop block-and-continue) — command handlers only, verify per runtime |
| payload mutation on supported events |
yes |
not documented |
| external callback program |
yes |
notify (dependable) + hooks.json command hooks (verify-first) |
| best use |
policy, verification, hygiene |
dependable: notify alerts/logging; experimental: lifecycle automation via hooks.json |
Typical Scenarios
Real situations mapped to the smallest event + recipe that solves them. Pick the narrowest row that matches.
| Scenario |
Event(s) |
Handler |
Recipe |
Block rm -rf, git push --force, git add . before they run |
PreToolUse (matcher Bash) |
command |
references/hook-templates.md, patterns §8/§11 |
| Auto-format and smoke-check after every edit, without slowing the agent |
PostToolUse (matcher Edit|Write, async: true) |
command |
patterns §1 |
| Require approval for production/destructive tools instead of hard-denying |
PreToolUse → permissionDecision: ask, PermissionRequest |
command |
patterns §8 |
| Inject repo state (branch, task id, top commands) at session start |
SessionStart / Setup |
command |
references/runtime-preflight-hooks.md, patterns §6 |
| Preserve critical state across context compaction |
PreCompact (checkpoint) + SessionStart (restore) |
command |
hook-templates.md, patterns §5 |
| Gate the next model call after a parallel tool burst |
PostToolBatch |
command/agent |
hook-templates.md, patterns §1 |
| Pass context into subagents and validate their output before they report back |
SubagentStart (inject) + SubagentStop (block) |
command |
hook-templates.md, SKILL.md §Subagent coordination |
| Keep an agent team from going idle prematurely |
TeammateIdle |
command |
Quick Reference |
| Per-worktree setup/teardown (caches, env files, indexes) |
WorktreeCreate / WorktreeRemove |
command |
hook-templates.md, patterns §7 |
| Ship audit events off-box to a SIEM/webhook immediately |
PostToolUse / Notification |
http |
patterns §2 |
| Semantic "are acceptance criteria met?" gate, not a syntactic one |
Stop / SubagentStop |
agent |
patterns §3 |
| Enforce a token/iteration/stagnation budget or kill-switch on an autonomous loop |
Stop / PostToolBatch / UserPromptSubmit |
command |
references/budget-and-loop-hooks.md |
| Audit edits to hook/approval/sandbox policy itself |
ConfigChange |
command |
hook-templates.md, patterns §4 |
Reload .envrc/local config when the directory changes |
CwdChanged / FileChanged |
command |
patterns §6 |
| Measure which skills actually trigger across sessions |
PreToolUse (matcher Read) |
command |
patterns §15 |
| Desktop alert / hand off "turn complete" to a local process (Codex) |
notify + tui.notifications |
external program |
patterns §12 |
Workflow
- Confirm whether the task is Claude hooks, Codex notifications, or mixed setup.
- Choose the minimum event surface that satisfies the requirement.
- Prefer deterministic command hooks for enforcement.
- Keep synchronous hooks fast; move heavy work into async or background paths.
- Validate event support and payload assumptions against current docs before final advice.
Validate and install checklist
# 1. Lint every hook script before deployment
shellcheck ~/.claude/hooks/*.sh
# 2. Dry-run a hook by piping a sample payload
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
| bash ~/.claude/hooks/preflight-guard.sh
# 3. Confirm hook files are executable
chmod +x ~/.claude/hooks/*.sh
# 4. Check audit log after a test session
cat /tmp/claude-hook-audit.log
# 5. Disable all hooks for emergency bypass
# Set "disableAllHooks": true in ~/.claude/settings.json
ASCII Flow
Hook request
-> Identify runtime
+-- Claude Code -> choose lifecycle event -> keep sync hook fast -> async heavy checks
+-- Codex -> configure notify/tui.notifications -> avoid lifecycle parity claims
-> Validate payload, paths, and secrets boundary
-> Test on the target runtime
-> Document event, command, failure mode, and rollback path
Event Surface (Claude Code, verified 2026-08-15)
Source: code.claude.com/docs/en/hooks
Session lifecycle
| Event |
Fires when |
Can block? |
SessionStart |
session begins or resumes |
no |
Setup |
one-time init (--init, --maintenance) |
no |
SessionEnd |
session terminates |
no |
Per-turn
| Event |
Fires when |
Can block? |
UserPromptSubmit |
user submits prompt, before Claude sees it |
yes |
UserPromptExpansion |
user-typed command expands into a prompt |
yes |
Stop |
Claude finishes responding |
yes |
StopFailure |
turn ends due to API error |
no |
MessageDisplay |
assistant text is displayed (display-only, does not alter transcript) |
no |
Tool execution
| Event |
Fires when |
Can block? |
PreToolUse |
before any tool call |
yes |
PermissionRequest |
permission dialog appears |
yes |
PermissionDenied |
tool denied by auto-mode classifier |
no |
PostToolUse |
after tool call succeeds |
no |
PostToolUseFailure |
after tool call fails |
no |
PostToolBatch |
after full batch of parallel tool calls resolves |
yes |
Elicitation |
MCP server requests user input |
yes |
ElicitationResult |
user responds to MCP elicitation |
yes |
Subagent / team
| Event |
Fires when |
Can block? |
SubagentStart |
subagent spawned |
no |
SubagentStop |
subagent finishes |
yes |
TeammateIdle |
agent-team teammate about to go idle |
yes |
Task
| Event |
Fires when |
Can block? |
TaskCreated |
task being created via TaskCreate |
yes |
TaskCompleted |
task being marked complete |
yes |
Config / filesystem
| Event |
Fires when |
Can block? |
ConfigChange |
config file changes during session |
yes (except policy_settings) |
InstructionsLoaded |
instruction files load (CLAUDE.md, includes, glob/path matches, on compact) |
no (observability; exit code ignored) |
CwdChanged |
working directory changes (cd) |
no |
DirectoryAdded |
a directory is added to the session workspace |
no |
FileChanged |
watched file changes on disk |
no |
WorktreeCreate |
worktree being created |
yes (any non-zero exit) |
WorktreeRemove |
worktree being removed |
no |
Compaction / display
| Event |
Fires when |
Can block? |
PreCompact |
before context compaction |
yes |
PostCompact |
after compaction completes |
no |
Notification |
Claude Code sends a notification |
no |
Hook Configuration Snippet
Minimal settings.json wiring (copy into ~/.claude/settings.json or .claude/settings.json):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/preflight-guard.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/post-audit.sh",
"async": true
}
]
}
],
"PreCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/pre-compact-state.sh"
}
]
}
]
}
}
Matcher rules: empty string or "*" = match all; |-separated words = exact list (e.g. "Edit|Write"); any string with other characters = JavaScript regex (e.g. "mcp__memory__.*").
Hook types: command (shell script), http (POST to URL), mcp_tool (call MCP server tool), prompt (single-turn LLM), agent (spawns subagent, experimental).
Exit codes: 0 = success, parse stdout for JSON; 2 = hard block, stderr fed to Claude; other non-zero = non-blocking error logged.
Payload mutation (exit 0 with JSON hookSpecificOutput): PreToolUse can set permissionDecision to allow / deny / ask / defer; PostToolUse can replace the tool result with updatedToolOutput; MessageDisplay can rewrite on-screen text with displayContent (screen only — transcript and Claude's view are unchanged). Always set hookEventName in the output to the firing event.
Exec vs. shell form: add "args": [...] to avoid shell and support cross-platform use. Omit args to use sh -c with pipes and &&.
Disable all hooks: set "disableAllHooks": true in settings for emergency bypass.
Choosing A Hook Type
| Type |
Latency |
Failure mode |
Use when |
command |
fastest (local process) |
script bug, wrong exit code, missing binary |
deterministic checks — the default choice |
http |
network round-trip |
endpoint down, timeout, no local fallback |
audit must leave the box immediately (SIEM, webhook) |
mcp_tool |
depends on server |
server not connected, tool schema drift |
policy lives on a connected MCP server already |
prompt |
one extra LLM call |
non-determinism, added token cost |
a single-turn semantic judgment is enough (no tool use needed) |
agent (experimental) |
slowest, most tokens |
cost/latency creep if used on hot paths |
multi-step semantic verification (e.g. "did this satisfy acceptance criteria") |
Escalate down this list only when the cheaper type can't express the check — see references/hook-patterns.md §3 for the command-vs-agent decision in practice.
Execution Model And Precedence
- Parallel, not sequential. When multiple registered hooks match the same event, Claude Code runs all of them in parallel. Do not assume one hook's output is visible to another, and do not rely on registration order to break ties. Identical
command hooks are deduplicated by command string + args; identical http hooks by URL — near-duplicate hooks (different flags, same intent) are not deduplicated and will double-fire.
- Conflicting decisions are not spec'd as "deny wins." The docs do not officially guarantee a resolution order when one matching hook returns
allow and another returns deny on the same event. Design as if any single deny should be treated as authoritative (fail-closed), and avoid registering two hooks with overlapping matchers that can disagree — narrow the matchers instead.
- Settings precedence (per code.claude.com/docs/en/settings, re-verified 2026-07-11): managed (org) policy > CLI flags > project
.claude/settings.local.json > project .claude/settings.json > user ~/.claude/settings.json. This corrects an earlier version of this skill, which put .claude/settings.local.json last — it actually overrides both project and user settings, not the reverse. Two more hook-bearing scopes exist beyond these four: plugin hooks/hooks.json (active whenever the plugin is enabled) and skill/agent frontmatter (active only while that component is active). Hooks from every scope merge and run together rather than override each other — a broader-scoped hook does not silently replace a narrower one — so this ordering mainly governs disableAllHooks and single-value settings conflicts, not whether a given hook fires.
allowManagedHooksOnly: an enterprise admin can set this in managed settings to block all user/project/plugin hooks except those bundled with plugins force-enabled via managed enabledPlugins. If a hook you registered mysteriously stops firing in a managed environment, check this first before debugging the hook script.
- Scoping a hook without a shell condition: tool-event hooks (
PreToolUse etc.) accept an if field — a permission-rule string like "if": "Bash(git *)" — to narrow when a handler fires beyond what matcher alone expresses. Prefer this over duplicating the same logic inside the script.
Recommended Patterns
Claude
SessionStart or Setup for runtime preflight
UserPromptSubmit to intercept or enrich prompts before Claude processes them
PreToolUse for narrow allow, deny, or ask guardrails
PostToolUse for formatting and smoke checks; PostToolUseFailure for failure-specific handling
PostToolBatch to gate the next model call after a parallel tool batch
SubagentStart to inject context into spawned subagents; SubagentStop to validate their output
PreCompact for terse state reinjection; PostCompact for post-compaction orientation
CwdChanged to reload .envrc or local configs when directory changes
ConfigChange for auditing hook-policy edits
WorktreeCreate and WorktreeRemove for worktree hygiene
- clear and re-register plugin hooks atomically during reloads so stale handlers never coexist with new ones
Subagent coordination
Inject context into spawned subagents via SubagentStart and validate their output via SubagentStop:
{
"hooks": {
"SubagentStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/subagent-context.sh"
}
]
}
],
"SubagentStop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/subagent-validate.sh"
}
]
}
]
}
}
SubagentStart command stdout is injected as systemMessage context for the subagent. SubagentStop can block the subagent from reporting back (exit 0 with decision: "block").
Codex
- use
notify for external callbacks (long-stable, dependable)
- use
tui.notifications for terminal notification policy
- for lifecycle automation, Codex now has
hooks.json (events incl. SessionStart, UserPromptSubmit, SubagentStop, Stop, PreCompact/PostCompact; docs) — but it is newer and has firing-reliability gaps (codex#17532). Verify it fires on the target runtime before depending on it; fall back to notify if dependability matters.
- Codex has no Claude-style
SessionEnd; use Stop for end-of-turn capture, and note Stop may fire per turn (dedupe if you need once-per-session semantics).
- do not assume full Claude-style lifecycle parity
Community Recipes
Third-party hooks worth knowing about, treat as community-sourced (verify provenance and review code before installing on production sessions):
monitoring/context-timeline (aitmpl.com, via Daniel San, 2026-04-26 thread) — installs with npx claude-code-templates@latest --hook monitoring/context-timeline. Shows a live timeline of the main agent's context window plus every subagent running in parallel, including the context each subagent returns when it finishes. Useful when debugging multi-worker fan-out or after enabling CLAUDE_CODE_FORK_SUBAGENT=1 (see agents-subagents §"Forking Parent Context Into Subagents"). Treat as observability, not policy enforcement — and audit the hook source before adding to a session that touches secrets.
Security Rules
- treat stdin JSON as untrusted input
- validate fields before use
- prefer canonical path checks over filename regex
- quote shell variables and avoid
eval
- keep secrets out of logs and checked-in config
- keep blocking hooks narrow and auditable
- run ShellCheck on non-trivial shell hooks
Known Traps
Assuming Claude lifecycle events and Codex notification surfaces are interchangeable.
Resolution: Check the Capability Boundary table above before wiring any hook. Claude has broad lifecycle events; Codex exposes only notify and tui.notifications. Test on the actual runtime before deploying.
Putting slow network calls, broad test suites, or repo-wide scans in always-on hooks.
Resolution: Move anything over ~200ms into an async or background path. Use PostToolUse with a background job (&) rather than blocking the tool call. Reserve synchronous hooks for fast, narrow checks.
Mutating files or config in a hook without leaving a reviewable diff or audit trail.
Resolution: Write mutations through the normal git-tracked file path. Log every mutation to an append-only audit file (e.g. /tmp/claude-hook-audit.log). See references/scenario-preflight-chain.md for a working example.
Reloading plugin hooks non-atomically and leaving stale handlers active beside new ones.
Resolution: Clear all handlers first, then register the new set. Never add new handlers before removing old ones. Use a lock file or atomic swap (write to a temp path, then mv) if the registration sequence can be interrupted.
Trusting payload shape, cwd, or path values without canonicalization and boundary checks.
Resolution: Always call realpath -m (or equivalent) on any path from the payload before using it. Validate that the resolved path is within the expected root before acting. Treat stdin JSON as untrusted input regardless of hook type.
Assuming two hooks on the same event run in order, or that "deny" is guaranteed to beat "allow" when they disagree.
Resolution: Claude Code runs all matching hooks in parallel with no documented tie-break rule. Narrow matchers so hooks on the same event cannot disagree, and treat any single deny as authoritative in your own hook logic rather than depending on runtime arbitration.
A hook silently stops firing after it worked fine in dev, and the script itself looks correct.
Resolution: Check settings precedence and allowManagedHooksOnly before debugging the script — an org-level managed policy can suppress user/project/plugin hooks entirely in a way that looks identical to a broken hook.
Anti-Patterns
- heavy synchronous test suites in every hook
- undocumented assumptions about Codex event parity
- regex-only path validation
- raw payload or environment logging without redaction
- silent dangerous rewrites that are not reviewable
Navigation
Resources
- references/hook-templates.md
- references/hook-patterns.md
- references/hook-security.md
- references/runtime-preflight-hooks.md
- references/scenario-preflight-chain.md — end-to-end runnable scenario: PreToolUse + PostToolUse + PreCompact composed
- references/budget-and-loop-hooks.md — budget, iteration cap, stagnation, kill-switch hooks for autonomous loops, triggered runs, and always-on bots
- assets/template-preflight-runtime-hook.sh
- data/sources.json
Related Skills
Fact-Checking
- Known bugs, regressions, framework/compiler/runtime footguns, and version-specific crash or workaround guidance must be verified against current primary web sources before being treated as current fact.
- Verify current hook behavior, event support, JSON schema, and Codex notification capabilities against official docs before operational guidance.
- Prefer official Claude and OpenAI sources over community posts.
- If live verification is unavailable, mark hook-surface claims as unverified.
Learnings Loop
Before applying this skill on a non-trivial task, read learnings.consolidated.md in this directory (and learnings.md if present).
After applying it, if you encountered a pattern worth remembering, a mistake worth preventing, or a domain fact that surprised you, append one dated bullet to learnings.md via agents-skills-feedback-loop/scripts/append_learning.py. Do not modify SKILL.md itself.
1---
2name: agents-hooks
3description: Configures Claude Code hooks and Codex hooks.json/notify callbacks. Use when adding guardrails, preflight, audit trails, worktree automation, or budget enforcement.
4compatibility: Claude Code + Codex. Claude Code hooks plus Codex notifications — runtime-specific invocation in both.
5version: "1.5"
6last_validated: 2026-08-15
7---
8
9# Claude Code Hooks + Codex Notifications
10
11Use this skill when hook behavior is the main concern: Claude lifecycle hooks, runtime preflight, guardrails, async verification, worktree lifecycle checks, subagent coordination, or Codex notification callbacks.
12
13Claude and Codex are not equivalent here. Claude has a broad, stable hook system (31 events, re-verified against official docs 2026-08-15). Codex has two surfaces: the long-stable notification surface (`notify` + `tui.notifications`), and a newer lifecycle-hooks system (`hooks.json` with events including `SessionStart`, `UserPromptSubmit`, `SubagentStop`, `Stop`, `PreCompact`/`PostCompact` — see [developers.openai.com/codex/hooks](https://developers.openai.com/codex/hooks)). The Codex hooks system is less mature than Claude's and has known firing-reliability gaps (e.g. [codex#17532](https://github.com/openai/codex/issues/17532): repo-local `.codex/config.toml` hooks may not fire in interactive sessions). Prefer `notify` for anything that must be dependable; treat Codex `hooks.json` as usable-but-verify and confirm it fires on your runtime before relying on it.
14
15## Quick Reference
16
17| Need | Event / Approach |
18|------|---------|
19| enforce pre-tool policy or guardrails | `PreToolUse` command hook |
20| fast runtime checks at session start | `SessionStart` or `Setup` |
21| react to user prompt before Claude sees it | `UserPromptSubmit` |
22| run checks after edits (non-blocking) | `PostToolUse` (async/background) |
23| catch tool failures separately | `PostToolUseFailure` |
24| check after a full batch of parallel tool calls | `PostToolBatch` |
25| inject context into or coordinate subagents | `SubagentStart` / `SubagentStop` |
26| transform what the user sees (not the transcript) | `MessageDisplay` |
27| persist compact durable state before compaction | `PreCompact` |
28| react after compaction completes | `PostCompact` |
29| handle worktree setup and teardown | `WorktreeCreate` / `WorktreeRemove` |
30| react to `cd` or watched file changes | `CwdChanged` / `FileChanged` |
31| keep agent team from going idle | `TeammateIdle` |
32| handle API errors at turn end | `StopFailure` |
33| reload plugin hooks safely | atomic clear-then-register swap |
34| add Codex callback behavior | `notify` external program plus `tui.notifications` |
35| enforce budgets, iteration caps, stagnation, kill-switches | [`references/budget-and-loop-hooks.md`](references/budget-and-loop-hooks.md) |
36
37## When To Use This Skill
38
39Use this skill when the task is:
40
41- building Claude hook automation
42- adding command guardrails or approval logic
43- wiring verification or audit hooks
44- managing hook-scoped repo hygiene
45- configuring Codex notifications or callback programs
46
47Route elsewhere when the main concern is:
48
49| Need | Use Instead |
50|------|-------------|
51| durable project memory or compaction content | [../agents-memory/SKILL.md](../agents-memory/SKILL.md) |
52| MCP design or server integration | [../agents-mcp/SKILL.md](../agents-mcp/SKILL.md) |
53| subagent design or delegation boundaries | `agents-subagents` |
54| multi-agent orchestration | [../agents-swarm-orchestration/SKILL.md](../agents-swarm-orchestration/SKILL.md) |
55| permission modes and approval routing for coding agents | [../ai-coding-agents-permissions/SKILL.md](../ai-coding-agents-permissions/SKILL.md) |
56
57## Capability Boundary
58
59| Capability | Claude Code | Codex |
60|-----------|-------------|-------|
61| broad lifecycle hooks | yes (stable, 31 events) | yes via `hooks.json` (newer, fewer events, reliability gaps) |
62| command or decision control | yes | yes (`PreToolUse` deny; `Stop`/`SubagentStop` block-and-continue) — `command` handlers only, verify per runtime |
63| payload mutation on supported events | yes | not documented |
64| external callback program | yes | `notify` (dependable) + `hooks.json` command hooks (verify-first) |
65| best use | policy, verification, hygiene | dependable: `notify` alerts/logging; experimental: lifecycle automation via `hooks.json` |
66
67## Typical Scenarios
68
69Real situations mapped to the smallest event + recipe that solves them. Pick the narrowest row that matches.
70
71| Scenario | Event(s) | Handler | Recipe |
72|----------|----------|---------|--------|
73| Block `rm -rf`, `git push --force`, `git add .` before they run | `PreToolUse` (matcher `Bash`) | `command` | [`references/hook-templates.md`](references/hook-templates.md), patterns §8/§11 |
74| Auto-format and smoke-check after every edit, without slowing the agent | `PostToolUse` (matcher `Edit\|Write`, `async: true`) | `command` | patterns §1 |
75| Require approval for production/destructive tools instead of hard-denying | `PreToolUse` → `permissionDecision: ask`, `PermissionRequest` | `command` | patterns §8 |
76| Inject repo state (branch, task id, top commands) at session start | `SessionStart` / `Setup` | `command` | [`references/runtime-preflight-hooks.md`](references/runtime-preflight-hooks.md), patterns §6 |
77| Preserve critical state across context compaction | `PreCompact` (checkpoint) + `SessionStart` (restore) | `command` | [`hook-templates.md`](references/hook-templates.md), patterns §5 |
78| Gate the next model call after a parallel tool burst | `PostToolBatch` | `command`/`agent` | [`hook-templates.md`](references/hook-templates.md), patterns §1 |
79| Pass context into subagents and validate their output before they report back | `SubagentStart` (inject) + `SubagentStop` (block) | `command` | [`hook-templates.md`](references/hook-templates.md), SKILL.md §Subagent coordination |
80| Keep an agent team from going idle prematurely | `TeammateIdle` | `command` | Quick Reference |
81| Per-worktree setup/teardown (caches, env files, indexes) | `WorktreeCreate` / `WorktreeRemove` | `command` | [`hook-templates.md`](references/hook-templates.md), patterns §7 |
82| Ship audit events off-box to a SIEM/webhook immediately | `PostToolUse` / `Notification` | `http` | patterns §2 |
83| Semantic "are acceptance criteria met?" gate, not a syntactic one | `Stop` / `SubagentStop` | `agent` | patterns §3 |
84| Enforce a token/iteration/stagnation budget or kill-switch on an autonomous loop | `Stop` / `PostToolBatch` / `UserPromptSubmit` | `command` | [`references/budget-and-loop-hooks.md`](references/budget-and-loop-hooks.md) |
85| Audit edits to hook/approval/sandbox policy itself | `ConfigChange` | `command` | [`hook-templates.md`](references/hook-templates.md), patterns §4 |
86| Reload `.envrc`/local config when the directory changes | `CwdChanged` / `FileChanged` | `command` | patterns §6 |
87| Measure which skills actually trigger across sessions | `PreToolUse` (matcher `Read`) | `command` | patterns §15 |
88| Desktop alert / hand off "turn complete" to a local process (Codex) | `notify` + `tui.notifications` | external program | patterns §12 |
89
90## Workflow
91
921. Confirm whether the task is Claude hooks, Codex notifications, or mixed setup.
932. Choose the minimum event surface that satisfies the requirement.
943. Prefer deterministic command hooks for enforcement.
954. Keep synchronous hooks fast; move heavy work into async or background paths.
965. Validate event support and payload assumptions against current docs before final advice.
97
98**Validate and install checklist**
99
100```bash
101# 1. Lint every hook script before deployment
102shellcheck ~/.claude/hooks/*.sh
103
104# 2. Dry-run a hook by piping a sample payload
105echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
106 | bash ~/.claude/hooks/preflight-guard.sh
107
108# 3. Confirm hook files are executable
109chmod +x ~/.claude/hooks/*.sh
110
111# 4. Check audit log after a test session
112cat /tmp/claude-hook-audit.log
113
114# 5. Disable all hooks for emergency bypass
115# Set "disableAllHooks": true in ~/.claude/settings.json
116```
117
118## ASCII Flow
119
120```text
121Hook request
122 -> Identify runtime
123 +-- Claude Code -> choose lifecycle event -> keep sync hook fast -> async heavy checks
124 +-- Codex -> configure notify/tui.notifications -> avoid lifecycle parity claims
125 -> Validate payload, paths, and secrets boundary
126 -> Test on the target runtime
127 -> Document event, command, failure mode, and rollback path
128```
129
130## Event Surface (Claude Code, verified 2026-08-15)
131
132Source: [code.claude.com/docs/en/hooks](https://code.claude.com/docs/en/hooks)
133
134**Session lifecycle**
135
136| Event | Fires when | Can block? |
137|-------|-----------|-----------|
138| `SessionStart` | session begins or resumes | no |
139| `Setup` | one-time init (`--init`, `--maintenance`) | no |
140| `SessionEnd` | session terminates | no |
141
142**Per-turn**
143
144| Event | Fires when | Can block? |
145|-------|-----------|-----------|
146| `UserPromptSubmit` | user submits prompt, before Claude sees it | yes |
147| `UserPromptExpansion` | user-typed command expands into a prompt | yes |
148| `Stop` | Claude finishes responding | yes |
149| `StopFailure` | turn ends due to API error | no |
150| `MessageDisplay` | assistant text is displayed (display-only, does not alter transcript) | no |
151
152**Tool execution**
153
154| Event | Fires when | Can block? |
155|-------|-----------|-----------|
156| `PreToolUse` | before any tool call | yes |
157| `PermissionRequest` | permission dialog appears | yes |
158| `PermissionDenied` | tool denied by auto-mode classifier | no |
159| `PostToolUse` | after tool call succeeds | no |
160| `PostToolUseFailure` | after tool call fails | no |
161| `PostToolBatch` | after full batch of parallel tool calls resolves | yes |
162| `Elicitation` | MCP server requests user input | yes |
163| `ElicitationResult` | user responds to MCP elicitation | yes |
164
165**Subagent / team**
166
167| Event | Fires when | Can block? |
168|-------|-----------|-----------|
169| `SubagentStart` | subagent spawned | no |
170| `SubagentStop` | subagent finishes | yes |
171| `TeammateIdle` | agent-team teammate about to go idle | yes |
172
173**Task**
174
175| Event | Fires when | Can block? |
176|-------|-----------|-----------|
177| `TaskCreated` | task being created via `TaskCreate` | yes |
178| `TaskCompleted` | task being marked complete | yes |
179
180**Config / filesystem**
181
182| Event | Fires when | Can block? |
183|-------|-----------|-----------|
184| `ConfigChange` | config file changes during session | yes (except `policy_settings`) |
185| `InstructionsLoaded` | instruction files load (CLAUDE.md, includes, glob/path matches, on compact) | no (observability; exit code ignored) |
186| `CwdChanged` | working directory changes (`cd`) | no |
187| `DirectoryAdded` | a directory is added to the session workspace | no |
188| `FileChanged` | watched file changes on disk | no |
189| `WorktreeCreate` | worktree being created | yes (any non-zero exit) |
190| `WorktreeRemove` | worktree being removed | no |
191
192**Compaction / display**
193
194| Event | Fires when | Can block? |
195|-------|-----------|-----------|
196| `PreCompact` | before context compaction | yes |
197| `PostCompact` | after compaction completes | no |
198| `Notification` | Claude Code sends a notification | no |
199
200## Hook Configuration Snippet
201
202Minimal `settings.json` wiring (copy into `~/.claude/settings.json` or `.claude/settings.json`):
203
204```json
205{
206 "hooks": {
207 "PreToolUse": [
208 {
209 "matcher": "Bash",
210 "hooks": [
211 {
212 "type": "command",
213 "command": "bash ~/.claude/hooks/preflight-guard.sh"
214 }
215 ]
216 }
217 ],
218 "PostToolUse": [
219 {
220 "matcher": "Edit|Write",
221 "hooks": [
222 {
223 "type": "command",
224 "command": "bash ~/.claude/hooks/post-audit.sh",
225 "async": true
226 }
227 ]
228 }
229 ],
230 "PreCompact": [
231 {
232 "matcher": "",
233 "hooks": [
234 {
235 "type": "command",
236 "command": "bash ~/.claude/hooks/pre-compact-state.sh"
237 }
238 ]
239 }
240 ]
241 }
242}
243```
244
245**Matcher rules**: empty string or `"*"` = match all; `|`-separated words = exact list (e.g. `"Edit|Write"`); any string with other characters = JavaScript regex (e.g. `"mcp__memory__.*"`).
246
247**Hook types**: `command` (shell script), `http` (POST to URL), `mcp_tool` (call MCP server tool), `prompt` (single-turn LLM), `agent` (spawns subagent, experimental).
248
249**Exit codes**: `0` = success, parse stdout for JSON; `2` = hard block, stderr fed to Claude; other non-zero = non-blocking error logged.
250
251**Payload mutation** (exit `0` with JSON `hookSpecificOutput`): `PreToolUse` can set `permissionDecision` to `allow` / `deny` / `ask` / `defer`; `PostToolUse` can replace the tool result with `updatedToolOutput`; `MessageDisplay` can rewrite on-screen text with `displayContent` (screen only — transcript and Claude's view are unchanged). Always set `hookEventName` in the output to the firing event.
252
253**Exec vs. shell form**: add `"args": [...]` to avoid shell and support cross-platform use. Omit `args` to use `sh -c` with pipes and `&&`.
254
255**Disable all hooks**: set `"disableAllHooks": true` in settings for emergency bypass.
256
257## Choosing A Hook Type
258
259| Type | Latency | Failure mode | Use when |
260|------|---------|--------------|----------|
261| `command` | fastest (local process) | script bug, wrong exit code, missing binary | deterministic checks — the default choice |
262| `http` | network round-trip | endpoint down, timeout, no local fallback | audit must leave the box immediately (SIEM, webhook) |
263| `mcp_tool` | depends on server | server not connected, tool schema drift | policy lives on a connected MCP server already |
264| `prompt` | one extra LLM call | non-determinism, added token cost | a single-turn semantic judgment is enough (no tool use needed) |
265| `agent` (experimental) | slowest, most tokens | cost/latency creep if used on hot paths | multi-step semantic verification (e.g. "did this satisfy acceptance criteria") |
266
267Escalate down this list only when the cheaper type can't express the check — see [`references/hook-patterns.md`](references/hook-patterns.md) §3 for the command-vs-agent decision in practice.
268
269## Execution Model And Precedence
270
271- **Parallel, not sequential.** When multiple registered hooks match the same event, Claude Code runs all of them in parallel. Do not assume one hook's output is visible to another, and do not rely on registration order to break ties. Identical `command` hooks are deduplicated by command string + `args`; identical `http` hooks by URL — near-duplicate hooks (different flags, same intent) are not deduplicated and will double-fire.
272- **Conflicting decisions are not spec'd as "deny wins."** The docs do not officially guarantee a resolution order when one matching hook returns `allow` and another returns `deny` on the same event. Design as if any single `deny` should be treated as authoritative (fail-closed), and avoid registering two hooks with overlapping matchers that can disagree — narrow the matchers instead.
273- **Settings precedence** (per [code.claude.com/docs/en/settings](https://code.claude.com/docs/en/settings), re-verified 2026-07-11): managed (org) policy > CLI flags > project `.claude/settings.local.json` > project `.claude/settings.json` > user `~/.claude/settings.json`. This corrects an earlier version of this skill, which put `.claude/settings.local.json` last — it actually overrides both project and user settings, not the reverse. Two more hook-bearing scopes exist beyond these four: plugin `hooks/hooks.json` (active whenever the plugin is enabled) and skill/agent frontmatter (active only while that component is active). Hooks from every scope merge and run together rather than override each other — a broader-scoped hook does not silently replace a narrower one — so this ordering mainly governs `disableAllHooks` and single-value settings conflicts, not whether a given hook fires.
274- **`allowManagedHooksOnly`**: an enterprise admin can set this in managed settings to block all user/project/plugin hooks except those bundled with plugins force-enabled via managed `enabledPlugins`. If a hook you registered mysteriously stops firing in a managed environment, check this first before debugging the hook script.
275- **Scoping a hook without a shell condition**: tool-event hooks (`PreToolUse` etc.) accept an `if` field — a permission-rule string like `"if": "Bash(git *)"` — to narrow when a handler fires beyond what `matcher` alone expresses. Prefer this over duplicating the same logic inside the script.
276
277## Recommended Patterns
278
279### Claude
280
281- `SessionStart` or `Setup` for runtime preflight
282- `UserPromptSubmit` to intercept or enrich prompts before Claude processes them
283- `PreToolUse` for narrow allow, deny, or ask guardrails
284- `PostToolUse` for formatting and smoke checks; `PostToolUseFailure` for failure-specific handling
285- `PostToolBatch` to gate the next model call after a parallel tool batch
286- `SubagentStart` to inject context into spawned subagents; `SubagentStop` to validate their output
287- `PreCompact` for terse state reinjection; `PostCompact` for post-compaction orientation
288- `CwdChanged` to reload `.envrc` or local configs when directory changes
289- `ConfigChange` for auditing hook-policy edits
290- `WorktreeCreate` and `WorktreeRemove` for worktree hygiene
291- clear and re-register plugin hooks atomically during reloads so stale handlers never coexist with new ones
292
293### Subagent coordination
294
295Inject context into spawned subagents via `SubagentStart` and validate their output via `SubagentStop`:
296
297```json
298{
299 "hooks": {
300 "SubagentStart": [
301 {
302 "matcher": "",
303 "hooks": [
304 {
305 "type": "command",
306 "command": "bash ~/.claude/hooks/subagent-context.sh"
307 }
308 ]
309 }
310 ],
311 "SubagentStop": [
312 {
313 "matcher": "",
314 "hooks": [
315 {
316 "type": "command",
317 "command": "bash ~/.claude/hooks/subagent-validate.sh"
318 }
319 ]
320 }
321 ]
322 }
323}
324```
325
326`SubagentStart` command stdout is injected as `systemMessage` context for the subagent. `SubagentStop` can block the subagent from reporting back (exit `0` with `decision: "block"`).
327
328### Codex
329
330- use `notify` for external callbacks (long-stable, dependable)
331- use `tui.notifications` for terminal notification policy
332- for lifecycle automation, Codex now has `hooks.json` (events incl. `SessionStart`, `UserPromptSubmit`, `SubagentStop`, `Stop`, `PreCompact`/`PostCompact`; [docs](https://developers.openai.com/codex/hooks)) — but it is newer and has firing-reliability gaps ([codex#17532](https://github.com/openai/codex/issues/17532)). Verify it fires on the target runtime before depending on it; fall back to `notify` if dependability matters.
333- Codex has no Claude-style `SessionEnd`; use `Stop` for end-of-turn capture, and note `Stop` may fire per turn (dedupe if you need once-per-session semantics).
334- do not assume full Claude-style lifecycle parity
335
336### Community Recipes
337
338Third-party hooks worth knowing about, treat as community-sourced (verify provenance and review code before installing on production sessions):
339
340- **`monitoring/context-timeline`** ([aitmpl.com](https://www.aitmpl.com/component/hook/monitoring/context-timeline), via Daniel San, 2026-04-26 [thread](https://x.com/dani_avila7/status/2048486242321662189)) — installs with `npx claude-code-templates@latest --hook monitoring/context-timeline`. Shows a live timeline of the main agent's context window plus every subagent running in parallel, including the context each subagent returns when it finishes. Useful when debugging multi-worker fan-out or after enabling `CLAUDE_CODE_FORK_SUBAGENT=1` (see `agents-subagents` §"Forking Parent Context Into Subagents"). Treat as observability, not policy enforcement — and audit the hook source before adding to a session that touches secrets.
341
342## Security Rules
343
344- treat stdin JSON as untrusted input
345- validate fields before use
346- prefer canonical path checks over filename regex
347- quote shell variables and avoid `eval`
348- keep secrets out of logs and checked-in config
349- keep blocking hooks narrow and auditable
350- run ShellCheck on non-trivial shell hooks
351
352## Known Traps
353
354- Assuming Claude lifecycle events and Codex notification surfaces are interchangeable.
355 Resolution: Check the Capability Boundary table above before wiring any hook. Claude has broad lifecycle events; Codex exposes only `notify` and `tui.notifications`. Test on the actual runtime before deploying.
356
357- Putting slow network calls, broad test suites, or repo-wide scans in always-on hooks.
358 Resolution: Move anything over ~200ms into an async or background path. Use `PostToolUse` with a background job (`&`) rather than blocking the tool call. Reserve synchronous hooks for fast, narrow checks.
359
360- Mutating files or config in a hook without leaving a reviewable diff or audit trail.
361 Resolution: Write mutations through the normal git-tracked file path. Log every mutation to an append-only audit file (e.g. `/tmp/claude-hook-audit.log`). See `references/scenario-preflight-chain.md` for a working example.
362
363- Reloading plugin hooks non-atomically and leaving stale handlers active beside new ones.
364 Resolution: Clear all handlers first, then register the new set. Never add new handlers before removing old ones. Use a lock file or atomic swap (write to a temp path, then `mv`) if the registration sequence can be interrupted.
365
366- Trusting payload shape, cwd, or path values without canonicalization and boundary checks.
367 Resolution: Always call `realpath -m` (or equivalent) on any path from the payload before using it. Validate that the resolved path is within the expected root before acting. Treat stdin JSON as untrusted input regardless of hook type.
368
369- Assuming two hooks on the same event run in order, or that "deny" is guaranteed to beat "allow" when they disagree.
370 Resolution: Claude Code runs all matching hooks in parallel with no documented tie-break rule. Narrow matchers so hooks on the same event cannot disagree, and treat any single `deny` as authoritative in your own hook logic rather than depending on runtime arbitration.
371
372- A hook silently stops firing after it worked fine in dev, and the script itself looks correct.
373 Resolution: Check settings precedence and `allowManagedHooksOnly` before debugging the script — an org-level managed policy can suppress user/project/plugin hooks entirely in a way that looks identical to a broken hook.
374
375## Anti-Patterns
376
377- heavy synchronous test suites in every hook
378- undocumented assumptions about Codex event parity
379- regex-only path validation
380- raw payload or environment logging without redaction
381- silent dangerous rewrites that are not reviewable
382
383## Navigation
384
385**Resources**
386
387- [references/hook-templates.md](references/hook-templates.md)
388- [references/hook-patterns.md](references/hook-patterns.md)
389- [references/hook-security.md](references/hook-security.md)
390- [references/runtime-preflight-hooks.md](references/runtime-preflight-hooks.md)
391- [references/scenario-preflight-chain.md](references/scenario-preflight-chain.md) — end-to-end runnable scenario: PreToolUse + PostToolUse + PreCompact composed
392- [references/budget-and-loop-hooks.md](references/budget-and-loop-hooks.md) — budget, iteration cap, stagnation, kill-switch hooks for autonomous loops, triggered runs, and always-on bots
393- [assets/template-preflight-runtime-hook.sh](assets/template-preflight-runtime-hook.sh)
394- [data/sources.json](data/sources.json)
395
396**Related Skills**
397
398- `agents-subagents`
399- [../agents-mcp/SKILL.md](../agents-mcp/SKILL.md)
400- [../agents-memory/SKILL.md](../agents-memory/SKILL.md)
401- [../agents-skills/SKILL.md](../agents-skills/SKILL.md)
402- [../agents-swarm-orchestration/SKILL.md](../agents-swarm-orchestration/SKILL.md)
403- [../ai-coding-agents-permissions/SKILL.md](../ai-coding-agents-permissions/SKILL.md)
404- [../ops-devops-platform/SKILL.md](../ops-devops-platform/SKILL.md)
405
406## Fact-Checking
407
408- Known bugs, regressions, framework/compiler/runtime footguns, and version-specific crash or workaround guidance must be verified against current primary web sources before being treated as current fact.
409- Verify current hook behavior, event support, JSON schema, and Codex notification capabilities against official docs before operational guidance.
410- Prefer official Claude and OpenAI sources over community posts.
411- If live verification is unavailable, mark hook-surface claims as unverified.
412
413## Learnings Loop
414
415Before applying this skill on a non-trivial task, read `learnings.consolidated.md` in this directory (and `learnings.md` if present).
416
417After applying it, if you encountered a pattern worth remembering, a mistake worth preventing, or a domain fact that surprised you, append one dated bullet to `learnings.md` via `agents-skills-feedback-loop/scripts/append_learning.py`. Do not modify `SKILL.md` itself.
418