Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: hooks description: Design, install, and debug Claude Code hooks (PreToolUse, PostToolUse, Notification, Stop, UserPromptSubmit, SessionStart). Use this skill whenever a user asks to "install hooks", "add a pre-tool hook", "format on save", "block dangerous commands", "protect sensitive files", "restore context after compact", "enforce tests before stop", or runs /cc-hooks. Also triggers on "hooks not firing", "hook keeps blocking", or any configuration of .claude/settings.json hook sections.
Hooks
Hooks are Claude Code's automation surface — deterministic bash (or any shell) scripts that fire on lifecycle events. Each hook receives JSON on stdin and returns JSON on stdout: {"decision": "approve"|"block", "reason"?: "..."}.
Events (matcher + input + output)
| Event | Fires | Input | Output shape | |
|---|---|---|---|---|
PreToolUse | Before any tool | {tool_name, tool_input} | {decision, reason?} — block prevents the tool call | |
PostToolUse | After tool succeeds | {tool_name, tool_input, tool_output} | {decision, reason?} | |
PostToolUseFailure | After tool fails | {tool_name, tool_input, error} | {decision} — almost always approve | |
Notification | Claude needs input | {message} | {decision} | |
Stop | Agent finishes turn | {stop_reason} | {decision} | |
UserPromptSubmit | Prompt submitted | {prompt} | {decision} + optional modified prompt | |
SessionStart | New session | {} | {decision} — usually for context injection |
Installing a hook pack
Use MCP cc_kb_hook_recipe(name) to fetch a security-hardened pack. Each pack returns:
script— the bash script contentscript_path— target path (.claude/hooks/{name}.sh)settings_snippet— the JSON to merge into.claude/settings.jsonverify— one-line manual test
Available pack names (fetch a shortlist first via cc_docs_hook_pack_recommend(signals)):
protect-sensitive-files— block writes to .env/credentials (always recommended)auto-format-after-edit— prettier/black/rustfmt on Write|Editstop-until-tests-pass— block Stop if tests failpost-compact-context-restoration— re-load memory rules after /compactdirenv-reload-on-cwd-change— reload .envrc on cdtask-created-governance— log new tasks to memorytask-completed-quality-gate— enforce lint+test before task completionteammate-idle-enforcement— prevent idle teammate processes
Authoring a new hook
#!/bin/bashset -euo pipefailINPUT=$(head -c 65536)# Validate JSON inputif ! printf '%s' "$INPUT" | jq -e . >/dev/null 2>&1; thenecho '{"decision":"approve"}'; exit 0fi# Extract fields you needFILE=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // ""')# Validate path (prevent injection + traversal)[ -z "$FILE" ] && { echo '{"decision":"approve"}'; exit 0; }REAL=$(realpath "$FILE" 2>/dev/null) || { echo '{"decision":"approve"}'; exit 0; }WD=$(realpath "$PWD")[[ "$REAL" != "$WD"/* ]] && { echo '{"decision":"approve"}'; exit 0; }BN=$(basename "$REAL")[[ "$BN" == -* ]] && { echo '{"decision":"approve"}'; exit 0; }# Do the work...# Always emit valid JSONecho '{"decision":"approve"}'
Safety rules:
- Always cap input with
head -c. - Always validate JSON before parsing.
- Always
realpath+ PWD-prefix check before touching a file. - Reject filenames starting with
-(flag injection). - Never
evalor unquoted-interpolate user content. - Default to
{"decision":"approve"}on any error — never block on bugs. - For concurrent writes to shared files (e.g. a log), use
flock.
Registering in settings.json
{"hooks": {"PreToolUse": [{"matcher": "Write|Edit","hooks": [{ "type": "command", "command": "bash .claude/hooks/protect-sensitive-files.sh" }]}],"PostToolUse": [{"matcher": "Write|Edit","hooks": [{ "type": "command", "command": "bash .claude/hooks/auto-format-after-edit.sh" }]}]}}
Matchers are regex. Write|Edit fires for both tools. * fires for all tools.
Debugging
- Hook not firing: check
.claude/settings.jsonhas the event + matcher. Matcher regex is case-sensitive. - Hook blocks unexpectedly: check the hook's stderr output — Claude Code prints it.
- Hook slow: profile with
time bash .claude/hooks/.... Target <100ms for PreToolUse, <500ms for PostToolUse. - JSON parse error: run
bash .claude/hooks/x.sh < fixture.jsonlocally.
MCP delegation
| Need | Tool | |
|---|---|---|
| Fetch a specific hook pack | cc_kb_hook_recipe(name) | |
| Recommend packs from signals | cc_docs_hook_pack_recommend({has_formatter, has_tests, has_secrets, has_git, ...}) |
Anti-patterns
- Hooks that call LLMs → slow and non-deterministic. Hooks should be fast local computation.
- Hooks that
rm -rfor delete files → useBashtool with user confirmation, not a hook. - Hooks that swallow errors without emitting approve → hangs the session.
- Multiple hooks on same event that fight each other → run them sequentially in one script or use distinct matchers.
Reference
- hook-event-matrix.md — full event → input → output contract per event