Documentation Index
Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
Use this file to discover all available pages before exploring further.
Hooks allow you to inject custom logic at key lifecycle points of an AI session, enabling audit logging, security controls, context injection, and dynamic behavior modification.
Event Overview
| Event | Trigger | Controllable Behavior |
|---|
PreToolUse | Before tool invocation | Intercept / allow / modify input |
PostToolUse | After tool succeeds | Audit / inject context / override output |
PostToolUseFailure | After tool fails | Error handling / logging |
UserPromptSubmit | Before user prompt is sent | Inject context / intercept |
SessionStart | Session begins | Initialize / inject context |
SessionEnd | Session ends | Cleanup / logging |
Stop | AI stops generating | Prevent stop, force continuation |
SubagentStart | Subagent starts | Observe / log |
SubagentStop | Subagent stops | Observe / log |
PreCompact | Before context compaction | Observe / log |
PostCompact | After context compaction | Observe / log |
CwdChanged | Working directory changes | Observe / log |
InstructionsLoaded | Instruction file loaded | Observe / log |
FileChanged | File created/modified/deleted | Observe / log |
PermissionRequest | Permission requested | Auto-approve / deny permission requests |
For complete event type definitions, see Hooks Reference.
Configuration
Configure hooks in QueryOptions.hooks:
import { query } from '@qoder-ai/qoder-agent-sdk';
import type { HookCallback, HookCallbackMatcher } from '@qoder-ai/qoder-agent-sdk';
const result = query({
prompt: 'perform task',
options: {
hooks: {
PreToolUse: [{ matcher: 'Bash', hooks: [myHook] }],
PostToolUse: [{ hooks: [auditHook] }],
SessionEnd: [{ hooks: [logHook] }],
},
},
});
for await (const message of result) {
// process messages
}
Matchers
The matcher field is a regex pattern — hooks only fire when the tool name matches:
const result = query({
prompt: 'perform task',
options: {
hooks: {
PreToolUse: [
{ matcher: 'Bash', hooks: [bashAuditHook] }, // Bash only
{ matcher: 'File.*|Write|Edit', hooks: [fileAuditHook] }, // File operations
{ hooks: [generalLogHook] }, // All tools (no matcher)
],
},
},
});
Callback Functions
Each hook callback receives the event input, tool use ID, and abort signal:
type HookCallback = (
input: HookInput,
toolUseID: string | undefined,
options: { signal: AbortSignal },
) => Promise<HookJSONOutput>;
All events share common fields: hook_event_name (event type), session_id (session ID), transcript_path (transcript file path), cwd (working directory). Each event also has event-specific fields, such as tool_name and tool_input for PreToolUse.
For complete input type definitions, see Hooks Reference.
Outputs
Callbacks return an object that controls behavior through these fields:
continue: false — Terminate the session
decision: "block" + reason — Block tool execution or prevent AI from stopping
hookSpecificOutput — Event-specific output, such as modifying tool input (updatedInput), overriding tool output (updatedToolOutput), or injecting context (additionalContext)
For complete output type definitions, see Hooks Reference.
Examples
Block dangerous shell commands:
const securityHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'PreToolUse') return {};
if (input.tool_name === 'Bash') {
const cmd = String((input.tool_input as any)?.command ?? '');
if (cmd.includes('rm -rf')) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: 'Destructive delete operations are not allowed',
},
};
}
}
return {};
};
Redact Sensitive Information (PostToolUse)
Override tool output to replace AK/Token and other sensitive information:
const secretRedactHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'PostToolUse') return {};
const content = typeof input.tool_response === 'string'
? input.tool_response
: JSON.stringify(input.tool_response);
const redacted = content
.replace(/(?:LTAI|AKID)[A-Za-z0-9]{16,}/g, '<REDACTED_AK>')
.replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/g, 'Bearer <REDACTED>');
if (redacted === content) return {};
return {
hookSpecificOutput: {
hookEventName: 'PostToolUse',
updatedToolOutput: redacted,
},
};
};
Truncate Long Output (PostToolUse)
Trim overly long Bash output, keeping head and tail:
const bashSummarizeHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'PostToolUse') return {};
if (input.tool_name !== 'Bash') return {};
const content = String(input.tool_response ?? '');
const THRESHOLD = 50 * 1024;
if (content.length <= THRESHOLD) return {};
const head = content.slice(0, 8 * 1024);
const tail = content.slice(-4 * 1024);
const omitted = content.length - head.length - tail.length;
return {
hookSpecificOutput: {
hookEventName: 'PostToolUse',
updatedToolOutput: `${head}\n\n[... OMITTED ${omitted} chars ...]\n\n${tail}`,
},
};
};
Force Continuation (Stop)
Prevent the AI from stopping when the task is incomplete:
const keepGoingHook: HookCallback = async (input) => {
if (input.hook_event_name !== 'Stop') return {};
if (!isTaskComplete()) {
return {
decision: 'block',
reason: 'Please continue completing the remaining tasks',
};
}
return {};
};
Auto-Approve Permissions (PermissionRequest)
Automatically approve Read tool permission requests:
const autoApproveRead: HookCallback = async (input) => {
if (input.hook_event_name !== 'PermissionRequest') return {};
if (input.tool_name === 'Read') {
return {
hookSpecificOutput: {
hookEventName: 'PermissionRequest',
decision: { behavior: 'allow' },
},
};
}
return {};
};
For the complete permission model, see Permissions.
Audit and Security Controls (Combined)
Combine audit logging with security interception:
import { query } from '@qoder-ai/qoder-agent-sdk';
import type { HookCallback } from '@qoder-ai/qoder-agent-sdk';
import * as fs from 'fs';
const auditLog = fs.createWriteStream('audit.log', { flags: 'a' });
const securityHook: HookCallback = async (input, toolUseID) => {
if (input.hook_event_name === 'PreToolUse') {
// Audit log
auditLog.write(JSON.stringify({
event: 'tool_call',
tool: input.tool_name,
input: input.tool_input,
timestamp: new Date().toISOString(),
}) + '\n');
// Security check: block curl to external domains
if (input.tool_name === 'Bash') {
const cmd = String((input.tool_input as any)?.command ?? '');
if (/curl\s+https?:\/\/(?!localhost)/.test(cmd)) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: 'HTTP requests to external domains are not allowed',
},
};
}
}
}
return {};
};
const result = query({
prompt: 'run deployment',
options: {
hooks: {
PreToolUse: [{ hooks: [securityHook] }],
},
},
});
for await (const message of result) {
// process messages
}
Notes
- Hook callbacks should return quickly to avoid blocking AI execution.
matcher uses JavaScript regex syntax, matching the tool_name field.
continue: false terminates the session — only effective for PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, SubagentStop events. Observation events (e.g. SessionEnd, CwdChanged) ignore this field.
- When multiple hooks return conflicting
decision values, "deny" / "block" takes precedence (strictest rule wins).
- When multiple hooks set
updatedToolOutput, the last non-empty value wins. For chained transforms (e.g. redact then truncate), execute them sequentially within a single callback.