Lesson 4 of 5 in Claude Code Configuration & Workflows

2.4 · Hooks — lifecycle & exit codes

Hooks are the deterministic gates in Claude Code. Where CLAUDE.md is suggestion, hooks are code — and code is enforcement. This lesson covers the lifecycle events, the exit-code contract that controls what happens, and the patterns hooks solve better than prompts.

Lifecycle events

Hooks fire on events like PreToolUse (before a tool runs), PostToolUse (after), Stop (session ends), Notification (something's asked of the user). Each hook is an external command whose exit code determines what happens next.

The exit code contract

Exit 0: allow, no message. Exit 2: block the action and surface stderr back to Claude as feedback so it can adjust. Other non-zero: an error occurred but action proceeds. Stdout goes to the user; stderr on exit 2 goes to Claude.

PreToolUse hook for a file write:
  if path starts with /secrets: echo 'blocked: protected path' >&2; exit 2
  else: exit 0

Hooks are deterministic, prompts are not

Anything you need to enforce absolutely — never touch this path, always run this formatter, log every tool call — belongs in a hook. Prompts are suggestions; hooks are code and code is enforcement.

Good to know — 'Add it to CLAUDE.md' is not equivalent to 'add a hook' when the requirement is absolute.

Common hook patterns

Audit logging on PreToolUse. Blocking writes to protected paths. Auto-running formatters and linters on PostToolUse for edits. Auto-running tests after code changes. Blocking access to files matching secret patterns.

PostToolUse hook after edit → run prettier + eslint --fix on the changed file

Takeaways

  • Exit 0 allows; exit 2 blocks and feeds stderr to Claude
  • Stdout → user; stderr on exit 2 → Claude
  • Hooks are deterministic gates; use them for absolute rules
  • Common uses: audit logs, safety nets, auto-lint, test runs

Exam traps

Returning opaque error messages
'Something went wrong' terminates useful work. Structured errors ({error: 'rate_limited', retry_after_seconds: 30}) let the model decide intelligently.
Retrying mutating operations without idempotency
Refunds, sends, and other mutating operations retried after a network timeout can double the side effect. Idempotency keys prevent this.
Silent failures that let the agent claim success
A tool that returns empty results on failure looks like a successful call with no data. Always return an explicit error, never an empty success.

Practice scenario

A mutating tool 'issue_refund' occasionally times out. The agent retries and sometimes the refund gets issued twice. What's the correct fix?

← PreviousNext →