The Notification and Hooks Pipeline

How events propagate through Claude Code — from lifecycle hooks that intercept tool calls to system reminders that steer model behavior.

Two event systems

Claude Code has two distinct event propagation mechanisms that serve different purposes:

  1. Hooks — user/extension-configurable interceptors that can block, modify, or log operations
  2. System reminders — model-facing behavioral instructions injected into conversation messages

Hooks: intercepting operations

The hooks system (src/utils/hooks.ts, 3789 lines) exposes 27 lifecycle events across the agent lifecycle: tool boundaries, session events, permissions, compaction, subagents, file changes, and MCP elicitation.

Event flow

Operation about to happen (e.g., BashTool execution)
  → Find matching hooks (settings.json priority: user > project > local > policy > plugin)
  → Run all matching hooks in parallel
  → Each hook receives JSON on stdin: session_id, cwd, tool_name, tool_input, etc.
  → Each hook responds via exit code + optional JSON on stdout
  → Merge responses: any block wins, modifications applied, context injected
  → Operation proceeds (or is blocked)

Four hook types

Type Execution Use case
Command Shell child process (bash/powershell) Linting, audit logging, CI integration
HTTP POST to URL with JSON payload External webhook systems
Prompt Single-turn LLM verification (Haiku) AI-based policy enforcement
Agent Multi-turn agent with tool access Complex verification workflows

The PreToolUse power

PreToolUse is the most powerful hook — it can: - Block the tool call (exit code 2, stderr sent to model) - Modify input (return updatedInput in JSON response) - Override permissions (return permissionDecision: allow/deny/ask)

This enables patterns like: "block all Bash commands matching rm -rf unless in /tmp" or "rewrite git push to always include --no-force."

Async hooks

Hooks can declare themselves async by emitting {"async": true} as their first stdout line. The operation proceeds immediately; the hook continues in the background. On completion, results are delivered via the async hook registry. A special asyncRewake mode lets hooks wake the model after long-running background checks.

Fail-open design

Non-exit-code-2 errors don't block operations. Timeouts kill the hook process and proceed. This ensures a broken hook never permanently blocks Claude Code.

System reminders: steering the model

System reminders are 40+ short behavioral instructions injected as <system-reminder> tags into user messages (not the system prompt). This design preserves the prompt cache — adding a reminder to a user message doesn't invalidate the static system prompt.

When reminders fire

The injection mechanism

prependUserContext() in src/utils/api.ts creates a user message with <system-reminder> content blocks containing CLAUDE.md, git status, current date, and other session context. This message is prepended to the conversation on every API call.

Additional reminders are injected by the message normalization pipeline — smooshSystemReminderSiblings folds reminder text blocks into adjacent tool results.

How hooks and reminders interact

Hooks are operational (they intercept and control what happens). Reminders are informational (they tell the model about context changes). They can trigger each other:

Environment variable injection

Special case: hooks for SessionStart, Setup, CwdChanged, and FileChanged receive CLAUDE_ENV_FILE. Bash hooks can write shell exports (export VAR=value), which accumulate into a session environment script injected into subsequent Bash tool commands. This enables dynamic environment configuration based on project context.

Cross-references