* Common logic for executing hooks * @param hookInput The structured hook input that will be validated and converted to JSON * @param toolUseID The ID for tracking this hook execution * @param matchQuery The query to match against hook matchers * @param signal Optional AbortSignal to cancel hook
({
hookInput,
toolUseID,
matchQuery,
signal,
timeoutMs = TOOL_HOOK_EXECUTION_TIMEOUT_MS,
toolUseContext,
messages,
forceSyncExecution,
requestPrompt,
toolInputSummary,
}: {
hookInput: HookInput
toolUseID: string
matchQuery?: string
signal?: AbortSignal
timeoutMs?: number
toolUseContext?: ToolUseContext
messages?: Message[]
forceSyncExecution?: boolean
requestPrompt?: (
sourceName: string,
toolInputSummary?: string | null,
) => (request: PromptRequest) => Promise<PromptResponse>
toolInputSummary?: string | null
})
| 1950 | * @returns Async generator that yields progress messages and hook results |
| 1951 | */ |
| 1952 | async function* executeHooks({ |
| 1953 | hookInput, |
| 1954 | toolUseID, |
| 1955 | matchQuery, |
| 1956 | signal, |
| 1957 | timeoutMs = TOOL_HOOK_EXECUTION_TIMEOUT_MS, |
| 1958 | toolUseContext, |
| 1959 | messages, |
| 1960 | forceSyncExecution, |
| 1961 | requestPrompt, |
| 1962 | toolInputSummary, |
| 1963 | }: { |
| 1964 | hookInput: HookInput |
| 1965 | toolUseID: string |
| 1966 | matchQuery?: string |
| 1967 | signal?: AbortSignal |
| 1968 | timeoutMs?: number |
| 1969 | toolUseContext?: ToolUseContext |
| 1970 | messages?: Message[] |
| 1971 | forceSyncExecution?: boolean |
| 1972 | requestPrompt?: ( |
| 1973 | sourceName: string, |
| 1974 | toolInputSummary?: string | null, |
| 1975 | ) => (request: PromptRequest) => Promise<PromptResponse> |
| 1976 | toolInputSummary?: string | null |
| 1977 | }): AsyncGenerator<AggregatedHookResult> { |
| 1978 | if (shouldDisableAllHooksIncludingManaged()) { |
| 1979 | return |
| 1980 | } |
| 1981 | |
| 1982 | if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { |
| 1983 | return |
| 1984 | } |
| 1985 | |
| 1986 | const hookEvent = hookInput.hook_event_name |
| 1987 | const hookName = matchQuery ? `${hookEvent}:${matchQuery}` : hookEvent |
| 1988 | |
| 1989 | // Bind the prompt callback to this hook's name and tool input summary so the UI can display context |
| 1990 | const boundRequestPrompt = requestPrompt?.(hookName, toolInputSummary) |
| 1991 | |
| 1992 | // SECURITY: ALL hooks require workspace trust in interactive mode |
| 1993 | // This centralized check prevents RCE vulnerabilities for all current and future hooks |
| 1994 | if (shouldSkipHookDueToTrust()) { |
| 1995 | logForDebugging( |
| 1996 | `Skipping ${hookName} hook execution - workspace trust not accepted`, |
| 1997 | ) |
| 1998 | return |
| 1999 | } |
| 2000 | |
| 2001 | const appState = toolUseContext ? toolUseContext.getAppState() : undefined |
| 2002 | // Use the agent's session ID if available, otherwise fall back to main session |
| 2003 | const sessionId = toolUseContext?.agentId ?? getSessionId() |
| 2004 | const matchingHooks = await getMatchingHooks( |
| 2005 | appState, |
| 2006 | sessionId, |
| 2007 | hookEvent, |
| 2008 | hookInput, |
| 2009 | toolUseContext?.options?.tools, |
no test coverage detected