Intercept and modify agent behavior at key execution points.
type HookEvent =
| 'PreToolUse' // Before tool execution
| 'PostToolUse' // After successful execution
| 'PostToolUseFailure' // After execution failure
| 'PermissionRequest' // During permission request
| 'UserPromptSubmit' // User submits prompt
| 'SessionStart' // Session starts
| 'SessionEnd' // Session ends
| 'Stop' // Execution stops
| 'SubagentStart' // Subagent starts
| 'SubagentStop' // Subagent stops
| 'PreCompact' // Before compaction
| 'Notification'; // System notificationtype HookCallback = (
input: HookInput,
toolUseID: string | undefined,
options: {signal: AbortSignal}
) => Promise<HookJSONOutput>;
interface HookCallbackMatcher {
matcher?: string;
hooks: HookCallback[];
timeout?: number;
}interface PreToolUseHookInput {
hook_event_name: 'PreToolUse';
tool_name: string;
tool_input: unknown;
tool_use_id: string;
session_id: string;
transcript_path: string;
cwd: string;
permission_mode?: string;
}
interface PostToolUseHookInput {
hook_event_name: 'PostToolUse';
tool_name: string;
tool_input: unknown;
tool_response: unknown;
tool_use_id: string;
// ... base fields
}
interface PostToolUseFailureHookInput {
hook_event_name: 'PostToolUseFailure';
tool_name: string;
tool_input: unknown;
tool_use_id: string;
error: string;
is_interrupt?: boolean;
// ... base fields
}
// See types.md for complete listinterface SyncHookJSONOutput {
continue?: boolean;
suppressOutput?: boolean;
stopReason?: string;
decision?: 'approve' | 'block';
systemMessage?: string;
reason?: string;
hookSpecificOutput?:
| {hookEventName: 'PreToolUse'; permissionDecision?: 'allow' | 'deny' | 'ask'; permissionDecisionReason?: string; updatedInput?: Record<string, unknown>}
| {hookEventName: 'UserPromptSubmit'; additionalContext?: string}
| {hookEventName: 'SessionStart'; additionalContext?: string}
| {hookEventName: 'PostToolUse'; additionalContext?: string; updatedMCPToolOutput?: unknown}
| {hookEventName: 'PostToolUseFailure'; additionalContext?: string}
| {hookEventName: 'PermissionRequest'; decision: {behavior: 'allow'; updatedInput?: Record<string, unknown>} | {behavior: 'deny'; message?: string; interrupt?: boolean}};
}
interface AsyncHookJSONOutput {
async: true;
asyncTimeout?: number;
}
type HookJSONOutput = SyncHookJSONOutput | AsyncHookJSONOutput;hooks: {
PreToolUse: [{
hooks: [async (input) => {
if (input.hook_event_name === 'PreToolUse') {
console.log(`[${input.tool_name}]`, input.tool_input);
}
return {continue: true};
}]
}],
PostToolUse: [{
hooks: [async (input) => {
if (input.hook_event_name === 'PostToolUse') {
console.log(`[${input.tool_name} ✓]`);
}
return {continue: true};
}]
}]
}hooks: {
PreToolUse: [{
hooks: [async (input) => {
if (input.hook_event_name === 'PreToolUse') {
if (input.tool_name === 'Bash' &&
(input.tool_input as any).command?.includes('rm -rf')) {
return {
continue: false,
stopReason: 'Dangerous command blocked',
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: 'rm -rf not allowed'
}
};
}
}
return {continue: true};
}]
}]
}hooks: {
SessionStart: [{
hooks: [async (input) => {
if (input.hook_event_name === 'SessionStart') {
return {
continue: true,
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: 'Project uses TypeScript. Follow TS best practices.'
}
};
}
return {continue: true};
}]
}]
}const result = query({
prompt: 'Build project',
options: {
hooks: {
PreToolUse: [{
hooks: [async (input) => {
if (input.hook_event_name === 'PreToolUse') {
console.log(`[PRE] ${input.tool_name}:`, input.tool_input);
}
return {continue: true};
}],
timeout: 5
}],
PostToolUse: [{
hooks: [async (input) => {
if (input.hook_event_name === 'PostToolUse') {
console.log(`[POST] ${input.tool_name} completed`);
}
return {continue: true};
}]
}],
PostToolUseFailure: [{
hooks: [async (input) => {
if (input.hook_event_name === 'PostToolUseFailure') {
console.error(`[FAIL] ${input.tool_name}:`, input.error);
}
return {continue: true};
}]
}],
SessionStart: [{
hooks: [async (input) => {
if (input.hook_event_name === 'SessionStart') {
console.log('Session started:', input.source);
return {
continue: true,
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: 'Use TypeScript best practices'
}
};
}
return {continue: true};
}]
}]
}
}
});
for await (const msg of result) {
if (msg.type === 'system' && msg.subtype === 'hook_response') {
console.log('Hook:', msg.hook_name, msg.hook_event);
if (msg.exit_code !== 0) {
console.error('Hook failed:', msg.stderr);
}
}
}const HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure", "Notification", "UserPromptSubmit", "SessionStart", "SessionEnd", "Stop", "SubagentStart", "SubagentStop", "PreCompact", "PermissionRequest"];