What this unit solvesHooks are deterministic scripts that run automatically at 30 lifecycle event points around tool execution, session boundaries, and prompt submission: formatting, validation, interception, safety nets. They shift “things that must happen every time” from soft model constraints to hard system guarantees. Since Claude Code v2.1, the hook system has expanded substantially: 30 events, five handler types (command / http / mcp_tool / prompt / agent), and exit code 2 blocking semantics. This unit covers the major trigger-point semantics,
settings.json configuration, practical templates, and the supply-chain risks that come with hooks as executable code.Learning objectives
- Name the 30 Claude Code hook events grouped by trigger timing, and state the semantics and typical use cases for PreToolUse, PostToolUse, Stop, and SessionStart.
- Write a correct
hooksblock insettings.jsonincludingmatcherandcommand, and distinguish the threematcherparsing rules. - Use
exit 2to block a PreToolUse tool call, and useadditionalContextto inject extra context for Claude. - Identify the supply-chain risks of hooks as external executable code and perform a basic scan before introducing a third-party hook.
- Wire this project’s
normalize_punctuation.pyandvalidate_kb --hookinto a deterministic safety net using hooks.
1. What a hook is: deterministic automation that fills the “might not remember” gap
CLAUDE.md and rules are “please follow this” (soft constraints, context layer). A hook is “the system enforces this” (hard guarantee, shell layer). It runs outside the model’s reasoning chain: the hook runs on your shell, reads the tool-input JSON from stdin, and decides whether to allow the tool call. The model does not know the hook is running by default. The exception is when you explicitly inform it via additionalContext.
Three key facts:
- Hooks are deterministic: they do not rely on the model “remembering” or “judging.” If the shell says exit 2, it exits 2. Every time.
- Hooks are executable code: installing a hook is equivalent to adding a shell command to your workflow. That is a supply-chain entry point.
- There are far more trigger points than most people realize: as of 2026-06, there are 30 events grouped into four timing categories [1].
2. The 30 trigger events grouped
Since Claude Code v2.1, the hook system covers 30 events ([1]), grouped into four timing categories.2.1 Once per session
2.2 Once per conversation turn
2.3 Once per tool call (inside the agentic loop)
2.4 Subagent / task events
2.5 Async / side-channel
3. Configuration: the settings.json structure
Hook configuration lives in the hooks block of settings.json, three levels deep: event, matcher group, hook handlers ([1]).
3.1 Five handler types
Thetype field determines how the handler runs ([1]):
The default
type is command. Omitting type is equivalent to explicitly specifying command.
3.2 Configuration locations and scope
3.3 Matcher syntax
Thematcher parsing rule is chosen automatically based on the characters present ([1]):
Each event matches a different field:
- Tool events (PreToolUse / PostToolUse, etc.) match
tool_name SessionStartmatches the source (startup/resume/clear/compact)Notificationmatches the notification typeFileChangedis a literal filename (not regex, not an exact-match list)- Events with no matcher mechanism (UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, MessageDisplay, CwdChanged): always fire, or always ignored if a matcher is set
3.4 Second-level filtering: if
The if field uses permission-rule syntax to provide finer-grained condition filtering ([1]):
"Bash(rm *)" and "Edit(*.ts)". && / || / list combinations are not supported. Split into multiple handlers for multi-condition logic.
4. Input: the JSON on stdin
Allcommand hooks receive a consistently structured JSON object from stdin ([1]); http hooks receive it as the POST body.
Base shape:
--agent or a Subagent, agent_id and agent_type are added. Only SessionStart receives the model field [1].
4.1 Path placeholders
Use theargs exec form to avoid quoting issues ([1]):
${CLAUDE_PROJECT_DIR}: project root${CLAUDE_PLUGIN_ROOT}: plugin installation directory${CLAUDE_PLUGIN_DATA}: plugin persistent data${user_config.*}: plugin user settings
5. Exit codes and decision-making
command hooks signal their intent through exit codes ([1]):
5.1 What exit 2 actually blocks
Not all events treat exit 2 as a block. Events that truly block ([1]):
PreToolUse→ blocks the tool callUserPromptSubmit→ rejects and clears the promptUserPromptExpansion→ blocks expansionStop/SubagentStop/TeammateIdle→ prevents stoppingPreCompact→ blocks compactPostToolBatch→ stops the agentic loop before the next model callWorktreeCreate→ any non-zero exit fails the creation
exit 2 has no blocking effect on PostToolUse / PostToolUseFailure / Notification, and only shows stderr. Unix intuition says “exit 1 is an error and should block,” but Claude Code’s hook system treats exit 1 as a non-blocking error. To actually block, use exit 2.
5.2 Injecting additional context for Claude
Return JSON withhookSpecificOutput.additionalContext to make Claude aware of something (the corresponding hookEventName is required) ([1]):
SessionStart/Setup/SubagentStart→ beginning of the conversationUserPromptSubmit/UserPromptExpansion→ attached to the promptPreToolUse/PostToolUse*/PostToolBatch→ attached to the tool result
5.3 General JSON output fields
5.4 Decision control modes (different events use different fields)
6. Five practical templates
6.1 PreToolUse: blocking a dangerous Bash command
settings.json:
6.2 PostToolUse: auto-formatting (this project)
Read the path from stdin, not from environment variablesThe script above scans for files to process on its own. To target “the file just edited,” parse
.tool_input.file_path from the hook input JSON on stdin (see the jq example in Section 4). Claude Code does not provide path-carrying environment variables like $CLAUDE_TOOL_INPUT_FILE_PATH (as of 2026-06). The only variables available for expansion inside command strings are addressing variables such as ${CLAUDE_PROJECT_DIR}, ${CLAUDE_PLUGIN_ROOT}, and ${CLAUDE_PLUGIN_DATA} [1].6.3 Stop: running wrap-up validation (this project)
validate_kb.py --hook runs in Stop hook mode: it blocks only on CRITICAL/HIGH findings, and fails open on tool errors so it does not disrupt normal flows. It serves as a deterministic safety net in this project: even if the natural-language-driven self-check is skipped, the hook will block at the end of the turn.
6.4 SessionStart: injecting environment variables
A SessionStart hook can persist environment variables via the$CLAUDE_ENV_FILE environment variable (which points to a session-specific temporary file) [1]:
SessionStart / Setup / CwdChanged / FileChanged support this).
6.5 Tool call logging (PostToolUse)
7. Hooks inside Skill / Subagent frontmatter
Hooks are not limited tosettings.json. Skills and Subagents can declare hooks in their frontmatter and those hooks are active only for the lifetime of that Skill / Subagent ([1]):
8. Advanced: HTTP / MCP / prompt handlers
8.1 HTTP handler template
allowedEnvVars is an allowlist: only the listed environment variables are exposed to the hook. Do not write values directly into headers (they end up in the plugin config file). Reference them as $VAR instead [1].
8.2 MCP tool handler template
8.3 Prompt handler template
9. Security: hooks are executable code; external sources are supply chain
Thecommand field of a hook is executed directly by the shell. Installing an unvetted hook is equivalent to handing execution rights over a piece of third-party code.
9.1 Known threats
- CVE-2025-59536 (CVSS 8.7): code inside
.claude/executes before the user approves the trust dialog. Fixed in Claude Code v1.0.111+ [2]. Unpatched versions are vulnerable to pre-planted malicious commands. - CVE-2026-21852: an attacker-controlled app overwrites
ANTHROPIC_BASE_URL, redirecting API traffic and exfiltrating the API key before trust is granted. Fixed in v2.0.65+ [2]. - Snyk ToxicSkills (2026-02): 36% of public skills contained prompt injection. Hooks and skills share the same supply-chain surface. Scan before installing.
9.2 Required scan before importing any external hook
9.3 Principle of least privilege
A hook’scommand should be granted only the access required to do its job:
- Do not read
~/.ssh,~/.aws, or**/.env*inside a hook. These paths belong inpermissions.deny. - Do not let hooks write outside the repo. Use
${CLAUDE_PROJECT_DIR}to stay inside the project. - Use
allowedEnvVarsto allowlist environment variables (HTTP handlers) rather than exposing the entire process environment.
9.4 Versioning and managed policy
disableAllHooks: truein settings temporarily disables all hooks (managed-level hooks can only be disabled by a managed-level setting) [1].- Organizations can set
allowManagedHooksOnly: trueto block user-level, project-level, and plugin-level hooks (except those force-enabled viaenabledPlugins). - Keep Claude Code >= v2.0.65 (CVE-2026-21852 fix) and >= v2.1.139 (hooks use
systemMessage/terminalSequenceinstead of writing directly to/dev/tty) [1, 2].
10. Tool comparison
Hook mechanism support varies widely across tools (as of 2026-06):Name clarifications and scope
- Claude Code’s hook system as of 2026-06 is a full 30-event lifecycle system ([1]). Other CLI tools’ “hooks” mostly refer to command hooks only, with far fewer events.
- OpenAI Codex, Antigravity, GitHub Copilot CLI hook mechanisms are subject to each vendor’s current documentation. Entries marked “source pending” above lack authoritative citations.
- Cursor as of 2026-06 does not provide a first-class hook mechanism in its CLI or IDE.
- This project’s
validate_kb --hook(Stop mode) is designed against the primary reference (Claude Code) semantics in this table.
11. Hands-on exercises
30-minute exercise
- PostToolUse auto-formatting: following this project’s
PostToolUsetemplate, wirenormalize_punctuation.pyinto.claude/settings.jsonand edit any.mdto observe automatic normalization. - PreToolUse blocking dangerous Bash: write
block-rm.shwith the corresponding settings. Try to have Claude runrm -rf /tmp/xxxand observe thepermissionDecision: denyresponse and the reason Claude receives. - Stop wrap-up validation: wire in
validate_kb.py --hook, deliberately create a unit with a frontmatter missing anupdatedfield, and observe the Stop hook blocking and reporting it. - Supply-chain scan: run the
rgscans from Section 9 against~/.claude/and.claude/and confirm there are no suspicious outbound references and no hidden Unicode. - SessionStart env injection: write a SessionStart hook that sets
MY_FLAG=1viaCLAUDE_ENV_FILE, then runecho $MY_FLAGin a new session to verify it takes effect.
12. Common pitfalls
Self-check
The bar for passing this unit
- Can you state the fundamental difference between a hook and
CLAUDE.md/ rules? (Shell determinism vs. context soft constraint) - Can you write a
PreToolUsehook that blocksgit commitwhen.envis staged, in under 5 minutes? - Can you state the semantics of
exit 0,exit 2, and other exit codes across different events? - Can you state the three supply-chain protection SOPs for hooks as executable code?
- In your current workflow, is there one thing that “should always happen but might be forgotten” that you could guarantee with a PostToolUse or Stop hook? Name the trigger event, the command to run, and the reasoning for choosing PostToolUse over Stop (or vice versa).
Sources and further reading
Factual claims are grounded in official documentation; fast-changing items are annotated as of 2026-05.-
[1] Anthropic, “Hooks,” code.claude.com, 2026. [Online]. Available: https://code.claude.com/docs/en/hooks (as of 2026-06; covers 30 events, five handler types, three-mode matcher parsing,
exit 2blocking semantics,additionalContextinjection,v2.1.139+switch toterminalSequence) - [2] Anthropic, “Claude Code release notes,” code.claude.com, 2026. [Online]. Available: https://code.claude.com/docs/en/changelog (as of 2026-06; CVE-2025-59536 and CVE-2026-21852 fix versions)
-
[3] Anthropic, “Extend Claude with skills,” code.claude.com, 2026. [Online]. Available: https://code.claude.com/docs/en/skills (as of 2026-06;
hooksconfiguration inside Skill frontmatter) -
[4] Anthropic, “Create custom subagents,” code.claude.com, 2026. [Online]. Available: https://code.claude.com/docs/en/subagents (as of 2026-06;
hooksinside Subagent frontmatter and automaticStoptoSubagentStopconversion)
- Configuration layer model: 02-1 The Configuration Layer Model.
- Division of responsibility between
CLAUDE.mdsoft constraints and hook hard guarantees: 04-1 CLAUDE.md and Memory Files. - The
rulespath-scoped mechanism: 04-2 Rules. - Hooks inside Skill frontmatter: 04-4 Skills.
- Subagent lifecycle hooks: 04-5 Subagents.
- Supply-chain risks: 03-3 Security, Privacy, and Supply-Chain Risk.