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.
See SDK References for the full event type definitions.
Here,
Configure hooks in the
In Java, each
The
Each hook callback receives the event input, the tool-use ID, and a context (an abort signal in TypeScript):
Java's
All events share common fields:
The callback returns an object / dict that controls behavior via:
Block dangerous shell commands:
Override tool output to replace AK/Token and other sensitive information:
Trim overly long Bash output, keeping head and tail:
Prevent the AI from stopping when the task is incomplete:
Automatically approve Read tool permission requests:
Combine audit logging with security interception:
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 |
PermissionDenied | Permission request denied | Observe / audit the denial |
WorktreeCreate (TypeScript SDK) | Managed worktree requested | Replace the built-in physical creation |
WorktreeRemove (TypeScript SDK) | Hook-created worktree is removed | Replace the built-in physical deletion |
Worktree replacement hooks (TypeScript)
WorktreeCreate and WorktreeRemove are resource replacement hooks, not read-only notifications. If WorktreeCreate is registered, the callback must physically create the worktree and return its path. The paired WorktreeRemove callback must physically delete that hook-created resource. Qoder CLI still owns the session cwd switch, transcript relocation, Resume state, and Exit lifecycle.
createWorktree and removeWorktree are application-provided helpers that perform the Git or host-specific operation; the SDK does not implement them.
Configuration
Configure hooks in the hooks field of options (in Java, SessionOptions.Builder.hooks(List<HookRegistration>)):
HookRegistration holds one event, an optional matcher, and one handler. To attach several handlers to the same matcher, register several entries. By default Java does not put Hook notifications into the message stream. With includeHookEvents(true), Hook notification messages such as HookEventMessage appear in the stream. This switch does not affect whether handlers run.
Matcher
The matcher field is a regex pattern — hooks only fire when the tool name matches:
Callback Functions
Each hook callback receives the event input, the tool-use ID, and a context (an abort signal in TypeScript):
HookHandler receives a single HookEvent: eventName() is the event name and payload() is the raw input JSON from the CLI; the tool call ID is in its tool_use_id field. Handlers run on a dedicated SDK Hook thread. Set the wait limit for a single registration with HookRegistration.Builder.timeoutMs(long); by default it uses controlRequestTimeoutMs.
Inputs
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. Java reads these fields from HookEvent.payload(). The SDK does not model or validate them, so allow for missing fields when reading.
See SDK References for full input type definitions.
Outputs
The callback returns an object / dict that controls behavior via:
continue: false— ends the session (Python field namecontinue_, serialized to JSON"continue")decision: "block"+reason— Block tool execution or prevent AI from stoppinghookSpecificOutput— Event-specific output, such as modifying tool input (updatedInput), overriding tool output (updatedToolOutput), or injecting context (additionalContext)
CompletionStage<JsonValue>. Build JSON with the same structure using JsonObject.builder(); the SDK passes it to the CLI as is. Return JsonObject.empty() to leave the event alone. The Java examples below cover only PreToolUse returning permissionDecision: "deny".
See SDK References for full output type definitions.
Example
Security Interception (PreToolUse)
Block dangerous shell commands:
Redact Sensitive Information (PostToolUse, TypeScript, Python)
Override tool output to replace AK/Token and other sensitive information:
Truncate Long Output (PostToolUse, TypeScript, Python)
Trim overly long Bash output, keeping head and tail:
Force Continuation (Stop, TypeScript, Python)
Prevent the AI from stopping when the task is incomplete:
Auto-Approve Permissions (PermissionRequest, TypeScript, Python)
Automatically approve Read tool permission requests:
For the complete permission model, see Permissions.
Audit and Security Controls (Combined)
Combine audit logging with security interception:
Notes
- Hook callbacks should return quickly to avoid blocking AI execution.
matchermatches thetool_namefield; regex syntax follows each language (JavaScript regex in TypeScript, theremodule in Python). Java passes the matcher to the CLI unchanged for matching.continue: false(Python:continue_: False) ends the session—effective only forPreToolUse,PostToolUse,PostToolUseFailure,UserPromptSubmit,Stop, andSubagentStop; observational events (e.g.SessionEnd,CwdChanged) ignore it.- When multiple hooks return conflicting
decisionvalues,"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. - The Python SDK uses trailing-underscore field names (
continue_) to avoid conflicts with Python keywords. The SDK automatically converts them to wire-protocol names (continue) during serialization.