Hook Creator
Caution
Hooks fail silently when the wrong output channel is used. On Stop and SubagentStop, use hookSpecificOutput.additionalContext to give Claude feedback and keep the turn going — plain additionalContext at the top level is not the correct channel for those events. PostToolUse runs after the tool has already executed, so its decision:"block" can never prevent the call — it only appends a reason next to the tool result. To rewrite what Claude actually sees, use hookSpecificOutput.updatedToolOutput instead. Getting the event-to-channel mapping wrong means the hook runs and produces zero effect, with no error to debug.
Tip
hook-creator knows all 31 events, 5 hook types, and the full routing matrix. Describe what you want in plain language — it picks the right event, output channel, and schema, then delivers a working file with fail-safe error handling included.
Quick reference
| Field | Value |
|---|---|
| Trigger | ”create hook”, “PreToolUse hook”, “debug hook” |
| Model | inherit — runs on the session’s model |
| Tools | Read, Write, Edit, Glob, Grep, Bash, WebFetch, WebSearch |
| Reference version | CC 2.1.233 — 31 events, 5 hook types |
| Output | Hook file (.sh or .mjs) + config snippet + verification checklist |
Scope guard
Hook Creator sizes the task before starting. One bounded unit = one deliverable, roughly 5 files, roughly 10 steps.
| Situation | What the agent does |
|---|---|
| Brief fits one bounded unit | Starts immediately |
| Brief exceeds ~5 files / ~10 steps, or bundles several independent deliverables | Stops before starting, returns a split proposal: 2-N bounded subtasks, each with scope and a suggested owner |
| Scope grows mid-flight | Stops at the next clean boundary, reports done / remaining / how to split |
| Brief is missing GOAL, SCOPE, CONTEXT, CONSUMER or acceptance | States the assumption explicitly in the report, or asks once — never invents scope |
“Set up a full hook suite for my repo” gets a split proposal. One hook, one event, one behavior gets built.
Tip
Got a split proposal instead of a hook? That is the guard working. Pick one hook from the proposal and re-send it, or spawn the subtasks as parallel agents in a single message.
When to use
- Create a new hook — you know what you want the hook to do, but not which event or output format to use
- Debug a broken hook — the hook runs but Claude ignores it, or the session is stuck
- Validate an existing hook — check routing, schema, fail-safe, and
stop_hook_activeguard - Choose a hook type — unsure whether to use
command,http,mcp_tool,prompt, oragenthook - Add context injection — inject project rules, mode state, or subagent instructions at session or tool boundaries
Examples
"Create a PreToolUse hook that blocks `rm -rf` commands"
"My Stop hook keeps blocking the session even after the task finishes.
The hook checks a state file but the session never exits."
"Write a SessionStart hook that injects the contents of .project-rules.md
into every new session as additionalContext"
Flow
- Clarify intent
Asks which event, which behavior (block / inject / modify / log), and where the hook config should live (project settings, global settings, or plugin hooks.json). Skips questions when intent is unambiguous.
- Select event and channel
Applies the routing matrix — picks the correct event from 27 options and the right output channel (
additionalContext,updatedInput,permissionDecision,decision,hookSpecificOutput) for your goal. Wrong channel = silent failure, so this step is non-negotiable. - Generate the hook file
Writes a
.sh(bash) or.mjs(Node.js) file from a production template. Always includes: stdin parsing, try/catch withoutput({})fallback,stop_hook_activeguard for Stop/SubagentStop hooks, and correct shebang. - Generate the config snippet
Produces the exact JSON block to paste into your target config file (
settings.json,settings.local.json, orhooks.json). Includes matcher, hook type, and command path. - Deliver verification checklist
Lists what to confirm: shebang, fail-safe catch, schema validity, executable bit, performance (<1s for blocking hooks). For debugging sessions, runs the hook manually with test input and explains what each output field does.
Event catalogue and routing matrix (expand for reference)
Output channel by event
| Goal | Channel | Event |
|---|---|---|
| Inject context for Claude | additionalContext | SessionStart, PreToolUse, UserPromptSubmit |
| Inject into subagent prompt | additionalContext | SubagentStart |
| Block tool execution | permissionDecision:"deny" | PreToolUse |
| Block session stop | decision:"block" + reason | Stop |
| Give Claude feedback at stop (keep turn going) | hookSpecificOutput.additionalContext | Stop, SubagentStop |
| Post-tool feedback (after the tool ran, cannot block it) | additionalContext | PostToolUse |
| Replace what Claude sees after a tool ran | hookSpecificOutput.updatedToolOutput | PostToolUse |
| Modify tool parameters | updatedInput | PreToolUse |
| Show user warning (Claude does NOT see) | systemMessage | Any |
| Auto-allow permission | decision.behavior:"allow" | PermissionRequest |
| Control teammates | {continue, stopReason} JSON | TeammateIdle, TaskCompleted, TaskCreated |
| Re-scan skill dirs on startup | reloadSkills: true | SessionStart (CC 2.1.152) |
| Set session title on startup/resume | hookSpecificOutput.sessionTitle | SessionStart (CC 2.1.152) |
additionalContext accumulates across every hook registered on the same event. updatedInput (and
updatedToolOutput on PostToolUse) is single-writer/last-wins — all hooks on the event see the same
original input and the runner keeps only the last one’s edit, so two hooks writing it clobber each
other. Prefer additionalContext for instruction injection; reserve updatedInput for a single
owning hook.
All 31 events
| Event | Blocking? | Key use |
|---|---|---|
| SessionStart | No | Context injection on start/resume; supports reloadSkills and sessionTitle outputs (CC 2.1.152); source fork for forked sessions (CC 2.1.214) |
| UserPromptSubmit | Yes | Gate or modify user prompts |
| PreToolUse | Yes | Allow / deny / modify tool calls |
| PermissionRequest | Yes | Auto-allow or auto-deny permission dialogs |
| PostToolUse | No | Feedback after tool completes — runs after the fact, decision:"block" adds a reason but cannot prevent the call, updatedToolOutput rewrites what Claude sees |
| PostToolUseFailure | No | React to failed tool call |
| SubagentStart | No | Inject into subagent context |
| SubagentStop | Yes | Block subagent exit; supports additionalContext to give Claude feedback |
| Stop | Yes | Block session exit until condition met; supports additionalContext to give Claude feedback (CC 2.1.163) |
| PreCompact | No | Write handoff before auto-compact |
| PostCompact | No | React after compact |
| Notification | No | Log or react to notifications (9 notification types) |
| TeammateIdle | Yes | Control agent team flow |
| TaskCompleted | Yes | Accept or re-assign tasks |
| TaskCreated | Yes | Intercept new tasks |
| ConfigChange | Yes | React to settings file changes |
| InstructionsLoaded | No | Track CLAUDE.md / rules file loads |
| FileChanged | No | React to file system changes |
| CwdChanged | No | React to directory changes |
| WorktreeCreate | Yes | Intercept worktree creation |
| WorktreeRemove | No | React to worktree removal |
| StopFailure | No | React to rate-limit / API errors (10 error types) |
| Elicitation | Yes | Auto-fill MCP forms |
| ElicitationResult | Yes | Post-elicitation hook |
| SessionEnd | No | Cleanup on session close |
| PermissionDenied | Yes | Retry after auto-mode denial (v2.1.89+) |
| MessageDisplay | No | Transform or hide assistant message text as it is displayed to the user (CC 2.1.152) |
| Setup | No | Fires only on --init-only / -p --init / -p --maintenance, never on normal startup — use SessionStart for per-session init |
| UserPromptExpansion | Yes | Blocks a /command from expanding — the path PreToolUse cannot reach when the user types /skillname directly |
| PostToolBatch | Yes | Fires once per batch of tool calls, before the next model call; stops the agentic loop on block |
| DirectoryAdded | No | Fires after --add-dir / /add-dir completes; continue is discarded, the directory is already added |
Hook types
| Type | Timeout | Description |
|---|---|---|
command | 600s | Shell/Node script; receives hook JSON on stdin, returns JSON on stdout. Deterministic, file I/O, external tools. |
http | 600s | POSTs the full hook JSON payload to a URL (axios); blocks for response; parses JSON body as hook output (decision / additionalContext). User-prompt field is named prompt in the payload. (v2.1.63+) |
mcp_tool | 600s | Invokes a tool on an already-configured MCP server synchronously; returned text parsed exactly like a command hook’s stdout JSON. Config: server (required), tool (required), input (optional; string values support ${...} interpolation from hook input JSON). |
prompt | 30s | Inline-LLM allow/block gate — evaluates the natural-language text, decides allow vs block+reason. Its text is NOT added to the model’s context. |
agent | 60s | LLM-agent allow/block gate, same semantics as prompt (allow or block+reason). NOT a general subagent whose output is injected. Experimental. |
prompt/agent = gates only (allow/block). command/http/mcp_tool = can both gate AND inject context.
For agent-type hooks: the spawned subagent inherits the session’s permissionMode. Never configure the parent session with --dangerously-skip-permissions just to avoid hook permission prompts — use a narrow permissionMode: acceptEdits on the hook’s agent definition instead. This applies to local agents only (.claude/agents/) — the plugin-agent parser ignores permissionMode and logs a warn at load time, so a hook agent that needs this key must be defined locally, not shipped inside a plugin.
Troubleshooting flags and environment variables (expand)
Safe mode and bundled skills
| Flag / Env var | Effect |
|---|---|
--safe-mode or CLAUDE_CODE_SAFE_MODE=1 | Starts CC with ALL customizations disabled: CLAUDE.md, plugins, skills, hooks, MCP servers. Use to isolate whether a hook is causing a startup problem. (CC 2.1.169) |
--disable-bundled-skills or CLAUDE_CODE_DISABLE_BUNDLED_SKILLS=1 | Hides all bundled skills, workflows, and built-in commands without disabling plugins or hooks. Useful when a bundled skill name collides with a project skill. |
Tip
--safe-mode is also the fastest way to verify a hook regression: if the problem disappears in safe mode, the hook is the cause.
Post-session lifecycle hook
A post-session lifecycle hook runs after a session ends and before the workspace is deleted. This is NOT a hooks.json event — do not add it to hooks.json; it will be silently ignored. It is available only on self-hosted runners as a workspace lifecycle callback configured at the runner level. (CC 2.1.169)
Return Contract
Verdict first, <=30 lines, path:line. No hook bodies, no stdin/stdout payload dumps, no CLAUDE_DEBUG transcripts, no preamble — one block per hook (file, event, matcher, purpose, routing, config location, test-fire result). The verification checklist above is the gate, not something to transcribe into the return; debug logs, full payloads, and failing runs go to .claude/reports/<YYYYMMDD-HHMMSS>_hook-creator/, path only.
/brewtools:agent-return-setup enforces this at ~1000 / ~2500 est-tokens when installed; the contract itself ships unconditionally.
Brewcode overview
All brewcode agents and skills — hooks live here in the plugin’s hooks/ directory.
GitHub source
Agent definition with full routing matrix, templates, and validation checklist.
Updating plugins
/brewtools:plugin-update to check and update the brewcode plugin suite in one command.
See the FAQ for details.