π File detail
utils/hooks/hookEvents.ts
π― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes HookStartedEvent, HookProgressEvent, HookResponseEvent, HookExecutionEvent, and HookEventHandler (and more) β mainly functions, hooks, or classes. Dependencies touch src. It composes internal code from debug (relative imports). What the file header says: Hook event system for broadcasting hook execution events. This module provides a generic event system that is separate from the main message stream. Handlers can register to receive events and decide what to do with them (e.g., convert to SDK messages, log, etc.).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
Hook event system for broadcasting hook execution events. This module provides a generic event system that is separate from the main message stream. Handlers can register to receive events and decide what to do with them (e.g., convert to SDK messages, log, etc.).
π€ Exports (heuristic)
HookStartedEventHookProgressEventHookResponseEventHookExecutionEventHookEventHandlerregisterHookEventHandleremitHookStartedemitHookProgressstartHookProgressIntervalemitHookResponsesetAllHookEventsEnabledclearHookEventState
π External import roots
Package roots from from "β¦" (relative paths omitted).
src
π₯οΈ Source preview
/**
* Hook event system for broadcasting hook execution events.
*
* This module provides a generic event system that is separate from the
* main message stream. Handlers can register to receive events and decide
* what to do with them (e.g., convert to SDK messages, log, etc.).
*/
import { HOOK_EVENTS } from 'src/entrypoints/sdk/coreTypes.js'
import { logForDebugging } from '../debug.js'
/**
* Hook events that are always emitted regardless of the includeHookEvents
* option. These are low-noise lifecycle events that were in the original
* allowlist and are backwards-compatible.
*/
const ALWAYS_EMITTED_HOOK_EVENTS = ['SessionStart', 'Setup'] as const
const MAX_PENDING_EVENTS = 100
export type HookStartedEvent = {
type: 'started'
hookId: string
hookName: string
hookEvent: string
}
export type HookProgressEvent = {
type: 'progress'
hookId: string
hookName: string
hookEvent: string
stdout: string
stderr: string
output: string
}
export type HookResponseEvent = {
type: 'response'
hookId: string
hookName: string
hookEvent: string
output: string
stdout: string
stderr: string
exitCode?: number
outcome: 'success' | 'error' | 'cancelled'
}
export type HookExecutionEvent =
| HookStartedEvent
| HookProgressEvent
| HookResponseEvent
export type HookEventHandler = (event: HookExecutionEvent) => void
const pendingEvents: HookExecutionEvent[] = []
let eventHandler: HookEventHandler | null = null
let allHookEventsEnabled = false
export function registerHookEventHandler(
handler: HookEventHandler | null,
): void {
eventHandler = handler
if (handler && pendingEvents.length > 0) {
for (const event of pendingEvents.splice(0)) {
handler(event)
}
}
}
function emit(event: HookExecutionEvent): void {
if (eventHandler) {
eventHandler(event)
} else {
pendingEvents.push(event)
if (pendingEvents.length > MAX_PENDING_EVENTS) {
pendingEvents.shift()
}
}
}
function shouldEmit(hookEvent: string): boolean {
if ((ALWAYS_EMITTED_HOOK_EVENTS as readonly string[]).includes(hookEvent)) {
return true
}
return (
allHookEventsEnabled &&
(HOOK_EVENTS as readonly string[]).includes(hookEvent)
)
}
export function emitHookStarted(
hookId: string,
hookName: string,
hookEvent: string,
): void {
if (!shouldEmit(hookEvent)) return
emit({
type: 'started',
hookId,
hookName,
hookEvent,
})
}
export function emitHookProgress(data: {
hookId: string
hookName: string
hookEvent: string
stdout: string
stderr: string
output: string
}): void {
if (!shouldEmit(data.hookEvent)) return
emit({
type: 'progress',
...data,
})
}
export function startHookProgressInterval(params: {
hookId: string
hookName: string
hookEvent: string
getOutput: () => Promise<{ stdout: string; stderr: string; output: string }>
intervalMs?: number
}): () => void {
if (!shouldEmit(params.hookEvent)) return () => {}
let lastEmittedOutput = ''
const interval = setInterval(() => {
void params.getOutput().then(({ stdout, stderr, output }) => {
if (output === lastEmittedOutput) return
lastEmittedOutput = output
emitHookProgress({
hookId: params.hookId,
hookName: params.hookName,
hookEvent: params.hookEvent,
stdout,
stderr,
output,
})
})
}, params.intervalMs ?? 1000)
interval.unref()
return () => clearInterval(interval)
}
export function emitHookResponse(data: {
hookId: string
hookName: string
hookEvent: string
output: string
stdout: string
stderr: string
exitCode?: number
outcome: 'success' | 'error' | 'cancelled'
}): void {
// Always log full hook output to debug log for verbose mode debugging
const outputToLog = data.stdout || data.stderr || data.output
if (outputToLog) {
logForDebugging(
`Hook ${data.hookName} (${data.hookEvent}) ${data.outcome}:\n${outputToLog}`,
)
}
if (!shouldEmit(data.hookEvent)) return
emit({
type: 'response',
...data,
})
}
/**
* Enable emission of all hook event types (beyond SessionStart and Setup).
* Called when the SDK `includeHookEvents` option is set or when running
* in CLAUDE_CODE_REMOTE mode.
*/
export function setAllHookEventsEnabled(enabled: boolean): void {
allHookEventsEnabled = enabled
}
export function clearHookEventState(): void {
eventHandler = null
pendingEvents.length = 0
allHookEventsEnabled = false
}