Patterns Observer / Event Bus See in Code Tour

Observer Pattern

Lifecycle hooks as a subprocess-based observer pattern — extensible without modifying core code.

Observer Pattern — Architecture Diagram
sequenceDiagram
    participant CC as Claude Code
    participant HE as Hook Executor
    participant H1 as Hook Script 1
    participant H2 as Hook Script 2
    CC->>HE: pre_tool_use event
    HE->>H1: spawn subprocess
    HE->>H2: spawn subprocess
    H1-->>HE: exit 0 (allow)
    H2-->>HE: exit 1 (deny)
    HE-->>CC: denied

Mermaid diagram definition

Deep Dive

The hooks system is an Observer pattern where the observers are shell scripts, not code objects. This has a powerful property: hooks can be written in any language, don't need to import anything, and can't crash Claude Code if they fail.

🔑Key Insight

Hooks are isolated by design — each runs in its own subprocess with no shared memory. A hook that takes 30 seconds doesn't block the UI. A hook that crashes doesn't affect other hooks.

⚠️Warning

`pre_tool_use` hooks can deny tool execution by exiting with a non-zero code. This gives users a way to implement custom permission policies — e.g., a hook that blocks all bash commands during a deployment window.

KEY TAKEAWAYS
  • Subprocess isolation prevents hooks from crashing the host
  • Any language can implement a hook — just a shell command
  • Pre-hook exit code determines allow/deny without code changes
  • SSRF guard protects hook URL configs from injection attacks

Source Code

Hook executor showing subprocess spawning, environment variable injection, and deny semantics.

// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
/**
 * Hooks are user-defined shell commands that can be executed at various points
 * in Claude Code's lifecycle.
 */
import { basename } from 'path'
import { spawn, type ChildProcessWithoutNullStreams } from 'child_process'
import { pathExists } from './file.js'
import { wrapSpawn } from './ShellCommand.js'
import { TaskOutput } from './task/TaskOutput.js'
import { getCwd } from './cwd.js'
import { randomUUID } from 'crypto'
import { formatShellPrefixCommand } from './bash/shellPrefix.js'
import {
  getHookEnvFilePath,
  invalidateSessionEnvCache,
} from './sessionEnvironment.js'
import { subprocessEnv } from './subprocessEnv.js'
import { getPlatform } from './platform.js'
import { findGitBashPath, windowsPathToPosixPath } from './windowsPaths.js'
import { getCachedPowerShellPath } from './shell/powershellDetection.js'
import { DEFAULT_HOOK_SHELL } from './shell/shellProvider.js'
import { buildPowerShellArgs } from './shell/powershellProvider.js'
import {
  loadPluginOptions,
  substituteUserConfigVariables,
} from './plugins/pluginOptionsStorage.js'
import { getPluginDataDir } from './plugins/pluginDirectories.js'
import {
  getSessionId,
  getProjectRoot,
  getIsNonInteractiveSession,
  getRegisteredHooks,
  getStatsStore,
  addToTurnHookDuration,
  getOriginalCwd,
  getMainThreadAgentType,
} from '../bootstrap/state.js'
import { checkHasTrustDialogAccepted } from './config.js'
import {
  getHooksConfigFromSnapshot,
  shouldAllowManagedHooksOnly,
  shouldDisableAllHooksIncludingManaged,
} from './hooks/hooksConfigSnapshot.js'
import {
  getTranscriptPathForSession,
  getAgentTranscriptPath,
} from './sessionStorage.js'
import type { AgentId } from '../types/ids.js'
import {
AI Assistant

Ask anything about Observer Pattern

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