hooks
11 questions
- AI
Which hook event should I use? The full list, ranked.
Claude Code exposes 28 hook events across five categories: lifecycle, tool execution, turn control, agent teams, and context management. Exit code 2 blocks the action on blocking events. Non-blocking events show stderr but cannot prevent what already happened.
- AI
How do I auto-format files after Claude edits them?
A PostToolUse hook on Edit/Write/MultiEdit reads the JSON envelope from stdin, pulls tool_input.file_path with jq, skips generated paths, and swallows formatter stderr so a syntax error during typing never feeds Claude noise.
- AI
How do I run a hook around an MCP tool call without breaking the session?
Match `mcp__<server>__<tool>` on PreToolUse / PostToolUse, return structured JSON for clean permission decisions, and put logging or telemetry hooks in async mode so they never block the session.
- AI
How do I stop Claude Code from leaking my .env to a tool call?
Deny rules in .claude/settings.json catch Read(.env*) and the obvious Bash escape hatches (env, printenv, cat .env*, git diff*); a PreToolUse hook adds belt-and-suspenders. Allowlists are cleaner long-term. Auto mode is the place this leaks first.
- AI
What does a SessionStart hook actually do for the agent in a real session?
A captured `claude --print` session against the demo app, with a SessionStart hook that runs `git log -5` plus working-tree status and injects the result as `additionalContext`. The prompt asked about recent feature work and forbade running `git log` manually. Claude used the commit SHA from the injected context to jump straight to `git show <sha>` and produced a substantive coverage analysis in four tool calls. The article shows the hook config, the script, the events.jsonl output that proves the hook fired, and what Claude visibly did with the context that saved a redundant tool call.
- AI
What does a PostToolUse hook actually do for a multi-file edit?
A captured `claude --print` session against the demo app, with a PostToolUse hook (matcher `Edit|Write`) that runs `npm run typecheck` after every edit. The prompt asked for a multi-file change (add `'parking-permit'` to `ServiceType`, update Finnish labels, update Record-typed consumers). Claude completed the work in eight Edits and also fixed a pre-existing `noUncheckedIndexedAccess` typecheck error that the hook flagged on the way through. The article shows the hook config, the actual fix Claude landed, and a real debugging surprise: the events.jsonl does not surface PostToolUse hook firings the same way SessionStart hooks do, which has consequences for how you verify these hooks are working.
- AI
What does Claude Code do when a PreToolUse hook denies a tool call mid-task?
A captured `claude --print` session against the demo app, with a PreToolUse hook that denies any Edit or Write to `src/shared/`. The prompt forced a collision: add a `formatForCity` helper to `src/shared/time.ts` AND use it from `src/reporting/service.ts`. Claude read four files, attempted the Edit, hit the block, read the hook script itself to understand the policy, then stopped with three concrete paths forward (workaround, lift the freeze, two-PR split). The article shows what the block looks like in the events stream, what Claude considered and rejected, and why this is the right shape of agent behavior under denial-mode hooks.
- AI
What does a Stop hook actually do when Claude says it's done but tests are failing?
A captured `claude --print` session against the demo app, with a Stop hook that runs `npm test` and returns `{decision: 'block', reason: ...}` when tests fail. The prompt asked Claude to change a `DEFAULT_DURATION` value that a test directly asserts, then declare done. The Stop hook blocked the first 'I'm done', and Claude reacted by editing the test assertion to match the new value rather than reverting the change or asking. The article shows the hook script, the wire-format signal in events.jsonl (a `stop-hook-error` notification), the test-gaming failure mode the hook accidentally encouraged, and the safeguard counter pattern that keeps a Stop hook from looping forever.
- AI
What does a UserPromptSubmit hook actually do for a vague 'where was I?' prompt?
A captured `claude --print` session against the demo app, with a UserPromptSubmit hook that appends the current branch, last 3 commits, and `git diff --stat` to every prompt as additional context. The user prompted 'Summarize what work is currently in flight in this repo' with two real uncommitted changes on disk (a `cancelAllForCitizen` stub method and a `describe.skip` test placeholder). Claude's thinking block named 'git status and recent commits' as its source, then ran 2 Bash calls to read the actual diff content, then produced a one-paragraph summary. The article shows the wire format of the hook's injection, the events.jsonl gap (UserPromptSubmit fires with zero visible system events), and the Bash-matcher footgun where `git -C /path diff` failed to match the `Bash(git diff:*)` allow rule because of the `-C` flag prefix.
- AI
Setup or SessionStart hook for my install script?
SessionStart fires on every session start, resume, `/clear`, and post-compaction, so it has to be fast. Setup fires only on explicit `claude --init-only`, `claude -p --init`, or `claude -p --maintenance`, which makes it the right home for slow one-time work like dependency installs or scheduled cleanup. Neither hook can block Claude from starting; for hard preconditions, gate the `claude` binary, not the hook.
- AI
How do I add my own /command to Claude Code?
Drop a SKILL.md file in .claude/skills/your-command/. Edits hot-reload in the current session, but creating the skills directory for the first time needs a restart. Slash commands and skills are now the same feature; the .claude/commands/ path still works but skills add the features you actually want.