The extensible event bus that powers lifecycle callbacks
// 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 {getSettings_DEPRECATED,
getSettingsForSource,
} from './settings/settings.js'
import {logEvent,
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
} from 'src/services/analytics/index.js'
import { logOTelEvent } from './telemetry/events.js'import { ALLOWED_OFFICIAL_MARKETPLACE_NAMES } from './plugins/schemas.js'import {startHookSpan,
endHookSpan,
isBetaTracingEnabled,
} from './telemetry/sessionTracing.js'
import {hookJSONOutputSchema,
promptRequestSchema,
type HookCallback,
type HookCallbackMatcher,
type PromptRequest,
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.
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.
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.
Ask anything about Hooks & Observer Pattern
Powered by Groq · Enter to send, Shift+Enter for newline