PreToolUse vs PostToolUse Hooks in context-mode: Complete Session Tracking Guide
PreToolUse hooks execute before tool invocation to enforce routing policies and modify requests, while PostToolUse hooks execute after completion to extract and persist session events to the SQLite database.
The context-mode repository implements a dual-hook architecture for intercepting AI assistant tool calls. Understanding the distinction between PreToolUse and PostToolUse hooks is essential for developers building session-aware applications that need to control execution flow and maintain persistent conversation history.
What Are PreToolUse Hooks?
PreToolUse hooks run immediately before an external tool executes. Located in platform-specific adapters like src/adapters/vscode-copilot/index.ts, these hooks receive the raw tool request containing tool_name and tool_input.
According to the source code in src/pi-extension.ts, the routing logic calls routing.routePreToolUse() to evaluate each request:
// src/pi-extension.ts
if (hookEventName === "PreToolUse") {
const decision = routing.routePreToolUse(
toolName,
toolInput,
projectDir,
"pi-extension"
);
// decision may contain `reject` or `additionalContext`
}
These hooks serve three primary purposes:
- Enforce allow-list policies: Block disallowed tools before execution
- Mutate requests: Inject additional context or rewrite arguments
- Abort execution: Return a
PreToolUseResponsewithreject: trueto prevent the call
Crucially, PreToolUse hooks do not write session data. They are purely advisory and influence how a session will look later without persisting anything to the database.
What Are PostToolUse Hooks?
PostToolUse hooks execute after a tool has finished and produced results. These hooks are the only mechanism for persisting session data in context-mode.
The src/session/extract.ts file transforms the raw PostToolUse payload into structured SessionEvent objects:
// src/session/extract.ts
export function extractPostToolUse(input: HookInput): SessionEvent[] {
const events: SessionEvent[] = [];
events.push(...extractFileAndRule(input));
events.push(...extractCwd(input));
events.push(...extractError(input));
return events;
}
Immediately after extraction, src/session/db.ts writes these events to the per-project SQLite database:
// src/session/db.ts
insertEvent(
sessionId: string,
event: SessionEvent,
sourceHook: string = "PostToolUse"
): void {
this.stmt(S.insertEvent).run(
sessionId,
event.type,
event.category,
event.priority,
event.data,
sourceHook,
Date.now()
);
}
PostToolUse hooks receive the tool output and any error information, enabling comprehensive session tracking of file reads, edits, directory changes, and failures.
Key Differences Between PreToolUse and PostToolUse
| Aspect | PreToolUse | PostToolUse |
|---|---|---|
| Execution timing | Before tool invocation | After tool completion |
| Input data | Raw tool request (tool_name, tool_input) |
Tool results (tool_response, errors) |
| Session persistence | No database writes | Writes to SQLite via SessionDB.insertEvent() |
| Control capabilities | Can reject or modify requests | Records immutable history |
| Adapter flags | preToolUse: boolean in src/adapters/types.ts |
postToolUse: boolean in src/adapters/types.ts |
The src/adapters/types.ts file defines these capabilities explicitly:
// src/adapters/types.ts
export interface AdapterCapabilities {
/** Platform supports PreToolUse / BeforeTool hooks. */
preToolUse: boolean;
/** Platform supports PostToolUse / AfterTool hooks. */
postToolUse: boolean;
}
Architectural Flow: How the Hooks Work Together
The complete lifecycle of a tool call in context-mode follows this sequence:
- Request interception: Adapter calls
parsePreToolUseInputto build aPreToolUseEvent - Policy enforcement:
routing.routePreToolUse()(used insrc/opencode-plugin.tsandsrc/openclaw-plugin.ts) evaluates permissions - Conditional execution: If allowed, the external tool runs with potentially modified inputs
- Result processing: Adapter calls
parsePostToolUseInputto create aPostToolUseEvent - Event extraction:
extractPostToolUse()insrc/session/extract.tsgeneratesSessionEventobjects - Persistence:
insertEvent()stores data withsourceHook: "PostToolUse"in the SQLite database
This dual-hook design provides complete visibility: PreToolUse controls access and augmentation before execution, while PostToolUse records actual outcomes for session-aware resumptions.
Summary
- PreToolUse hooks execute before tool calls in
src/pi-extension.tsand related plugins to enforce routing policies, mutate inputs, or reject requests entirely without persisting data. - PostToolUse hooks execute after completion, serving as the sole mechanism for session tracking by extracting events in
src/session/extract.tsand persisting them viasrc/session/db.ts. - Both hooks implement capability flags defined in
src/adapters/types.tsand require platform-specific adapters to parse inputs and format responses. - Only PostToolUse writes to the session database, making it the authoritative source for conversation history.
Frequently Asked Questions
Can PreToolUse hooks modify the tool input before execution?
Yes. PreToolUse hooks can mutate requests by returning a PreToolUseResponse containing overrideInput or additionalContext. This allows the system to inject project-wide context snippets or rewrite arguments before the tool receives them, as implemented in the routing logic across src/opencode-plugin.ts and src/openclaw-plugin.ts.
Why don't PreToolUse hooks write session data?
PreToolUse hooks are advisory-only because they execute before the tool produces results. Since no outcome exists yet, there is nothing to record for session history. The database writes occur exclusively in PostToolUse through SessionDB.insertEvent() with the explicit sourceHook parameter set to "PostToolUse".
Which adapters support these hooks?
All platform adapters in the src/adapters/ directory support both hooks, including VS Code Copilot, OpenClaw, and Kiro implementations. Each adapter exposes preToolUse and postToolUse boolean flags in the AdapterCapabilities interface and provides parsePreToolUseInput, formatPreToolUseResponse, and their PostToolUse counterparts.
How does context-mode handle tool rejections?
When routing.routePreToolUse() determines a tool should be blocked (such as a dangerous Bash command), it returns a PreToolUseResponse with reject: true. The adapter formats this response back to the client, preventing execution entirely without creating any session record in the database.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →