Chapter 6 System Design

Hooks & Observer Pattern

The extensible event bus that powers lifecycle callbacks

src/utils/hooks.tsLines 170
1
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
2
/**
3
 * Hooks are user-defined shell commands that can be executed at various points
4
 * in Claude Code's lifecycle.
5
 */
6
import { basename } from 'path'
7
import { spawn, type ChildProcessWithoutNullStreams } from 'child_process'
8
import { pathExists } from './file.js'
9
import { wrapSpawn } from './ShellCommand.js'
10
import { TaskOutput } from './task/TaskOutput.js'
11
import { getCwd } from './cwd.js'
12
import { randomUUID } from 'crypto'
13
import { formatShellPrefixCommand } from './bash/shellPrefix.js'
14
import {
15
  getHookEnvFilePath,
16
  invalidateSessionEnvCache,
17
} from './sessionEnvironment.js'
18
import { subprocessEnv } from './subprocessEnv.js'
19
import { getPlatform } from './platform.js'
20
import { findGitBashPath, windowsPathToPosixPath } from './windowsPaths.js'
21
import { getCachedPowerShellPath } from './shell/powershellDetection.js'
22
import { DEFAULT_HOOK_SHELL } from './shell/shellProvider.js'
23
import { buildPowerShellArgs } from './shell/powershellProvider.js'
24
import {
25
  loadPluginOptions,
26
  substituteUserConfigVariables,
27
} from './plugins/pluginOptionsStorage.js'
28
import { getPluginDataDir } from './plugins/pluginDirectories.js'
29
import {
30
  getSessionId,
31
  getProjectRoot,
32
  getIsNonInteractiveSession,
33
  getRegisteredHooks,
34
  getStatsStore,
35
  addToTurnHookDuration,
36
  getOriginalCwd,
37
  getMainThreadAgentType,
38
} from '../bootstrap/state.js'
39
import { checkHasTrustDialogAccepted } from './config.js'
40
import {
41
  getHooksConfigFromSnapshot,
42
  shouldAllowManagedHooksOnly,
43
  shouldDisableAllHooksIncludingManaged,
44
} from './hooks/hooksConfigSnapshot.js'
45
import {
46
  getTranscriptPathForSession,
47
  getAgentTranscriptPath,
48
} from './sessionStorage.js'
49
import type { AgentId } from '../types/ids.js'
50
import {
51
  getSettings_DEPRECATED,
52
  getSettingsForSource,
53
} from './settings/settings.js'
54
import {
55
  logEvent,
56
  type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
57
} from 'src/services/analytics/index.js'
58
import { logOTelEvent } from './telemetry/events.js'
59
import { ALLOWED_OFFICIAL_MARKETPLACE_NAMES } from './plugins/schemas.js'
60
import {
61
  startHookSpan,
62
  endHookSpan,
63
  isBetaTracingEnabled,
64
} from './telemetry/sessionTracing.js'
65
import {
66
  hookJSONOutputSchema,
67
  promptRequestSchema,
68
  type HookCallback,
69
  type HookCallbackMatcher,
70
  type PromptRequest,
Annotations (click the dots)

src/utils/hooks.ts implements a lifecycle hook system. Users configure hook scripts in settings.json, and Claude Code fires them at specific lifecycle events — before a tool runs, after it completes, when a session starts, etc.

🔑Key Insight

Hooks are subprocess-based — each hook is a shell command that runs in a child process. This isolation means a buggy hook cannot crash Claude Code, and it enforces a clean boundary between extension logic and the core.

The pre_tool_use hook receives context like the tool name and current directory via environment variables. A hook can signal "deny" by exiting with a non-zero code, giving users a way to implement custom permission policies.

ℹ️Info

The SSRF guard (`src/utils/hooks/ssrfGuard.ts`) validates URLs in hook configurations — blocking `localhost`, `169.254.x.x` (AWS metadata), and other local addresses before a hook request fires.

KEY TAKEAWAYS
  • Hooks are shell scripts configured in settings.json — not code in the codebase
  • pre_tool_use and post_tool_use fire before/after every tool execution
  • Hooks run in subprocesses — they cannot directly mutate application state
  • SSRF guard validates URLs in hook configs to prevent supply-chain attacks
  • The AsyncHookRegistry manages non-blocking hooks for long-running operations
AI Assistant

Ask anything about Hooks & Observer Pattern

Powered by Groq · Enter to send, Shift+Enter for newline