SDK Hook System
Version Requirements: This documentation is for CodeBuddy Agent SDK v0.1.0 and above.
This document explains how to use the Hook system in the SDK to insert custom logic before and after tool execution.
Table of Contents
Overview
Hooks allow you to insert custom logic during CodeBuddy's session lifecycle, enabling:
- Validation and interception before tool calls
- Logging after tool execution
- Review of user-submitted content
- Initialization and cleanup at session start/end
Supported Events
| Event | Trigger Timing |
|---|---|
PreToolUse | Before tool execution |
PostToolUse | After successful tool execution |
UserPromptSubmit | When user submits a message |
Stop | When main Agent response ends |
SubagentStop | When sub-agent ends |
PreCompact | Before context compression |
Hook Configuration
Configure hooks through the hooks option. Each event can have multiple matchers, and each matcher can have multiple hook callbacks.
Basic Structure
typescript
import { query } from '@tencent-ai/agent-sdk';
const q = query({
prompt: 'Help me analyze the code',
options: {
model: 'deepseek-v3.1',
hooks: {
PreToolUse: [
{
matcher: 'Bash', // Only match Bash tool
hooks: [
async (input, toolUseId, ctx) => {
console.log('About to execute:', input);
return { continue: true };
}
],
timeout: 5000 // Timeout in milliseconds
}
]
}
}
});python
from codebuddy_agent_sdk import query, CodeBuddyAgentOptions, HookMatcher
async def pre_tool_hook(input_data, tool_use_id, context):
print(f"About to execute: {input_data}")
return {"continue_": True}
options = CodeBuddyAgentOptions(
model="deepseek-v3.1",
hooks={
"PreToolUse": [
HookMatcher(
matcher="Bash", # Only match Bash tool
hooks=[pre_tool_hook],
timeout=5.0 # Timeout in seconds
)
]
}
)
async for msg in query(prompt="Help me analyze the code", options=options):
print(msg)HookMatcher Structure
| Field | Type | Description |
|---|---|---|
matcher | string | Match pattern, supports regex. * or empty string matches all |
hooks | HookCallback[] | Array of callback functions |
timeout | number | Timeout (milliseconds in TypeScript, seconds in Python) |
Matcher Patterns
- Exact match:
"Bash"only matches Bash tool - Regex match:
"Edit|Write"matches Edit or Write - Wildcard:
"*"or""matches all tools - Prefix match:
"mcp__.*"matches all MCP tools
Event Types
PreToolUse
Triggered before tool execution, can block execution or modify input.
typescript
hooks: {
PreToolUse: [{
matcher: 'Bash',
hooks: [
async (input, toolUseId, ctx) => {
const command = input.command as string;
// Block dangerous commands
if (command.includes('rm -rf')) {
return {
decision: 'block',
reason: 'Dangerous command blocked'
};
}
return { continue: true };
}
]
}]
}python
async def pre_bash_hook(input_data, tool_use_id, context):
command = input_data.get("command", "")
# Block dangerous commands
if "rm -rf" in command:
return {
"decision": "block",
"reason": "Dangerous command blocked"
}
return {"continue_": True}
hooks = {
"PreToolUse": [
HookMatcher(matcher="Bash", hooks=[pre_bash_hook])
]
}PostToolUse
Triggered after successful tool execution, can add extra context.
typescript
hooks: {
PostToolUse: [{
matcher: 'Write|Edit',
hooks: [
async (input, toolUseId) => {
console.log(`File modified: ${input.file_path}`);
// Log file changes
await logFileChange(input.file_path);
return { continue: true };
}
]
}]
}python
async def post_write_hook(input_data, tool_use_id, context):
print(f"File modified: {input_data.get('file_path')}")
# Log file changes
await log_file_change(input_data.get("file_path"))
return {"continue_": True}
hooks = {
"PostToolUse": [
HookMatcher(matcher="Write|Edit", hooks=[post_write_hook])
]
}UserPromptSubmit
Triggered when user submits a message, can add context or block processing.
typescript
hooks: {
UserPromptSubmit: [{
hooks: [
async (input) => {
const prompt = input.prompt as string;
// Sensitive word check
if (containsSensitiveWords(prompt)) {
return {
decision: 'block',
reason: 'Message contains sensitive content'
};
}
return { continue: true };
}
]
}]
}python
async def prompt_check_hook(input_data, tool_use_id, context):
prompt = input_data.get("prompt", "")
# Sensitive word check
if contains_sensitive_words(prompt):
return {
"decision": "block",
"reason": "Message contains sensitive content"
}
return {"continue_": True}
hooks = {
"UserPromptSubmit": [
HookMatcher(hooks=[prompt_check_hook])
]
}Stop / SubagentStop
Triggered when Agent response ends, can block stopping and request continuation.
typescript
hooks: {
Stop: [{
hooks: [
async (input) => {
// Check if task is truly complete
if (!isTaskComplete()) {
return {
decision: 'block',
reason: 'Task incomplete, please continue'
};
}
return { continue: true };
}
]
}]
}python
async def stop_hook(input_data, tool_use_id, context):
# Check if task is truly complete
if not is_task_complete():
return {
"decision": "block",
"reason": "Task incomplete, please continue"
}
return {"continue_": True}
hooks = {
"Stop": [HookMatcher(hooks=[stop_hook])]
}Hook Input
The input structure received by hook callbacks varies by event type.
Common Fields
json
{
"session_id": "abc123",
"cwd": "/path/to/project",
"permission_mode": "default",
"hook_event_name": "PreToolUse"
}PreToolUse / PostToolUse Input
json
{
"tool_name": "Bash",
"tool_input": {
"command": "ls -la"
}
}UserPromptSubmit Input
json
{
"prompt": "Help me write a function"
}Stop / SubagentStop Input
json
{
"stop_hook_active": false
}Hook Output
The output returned by hook callbacks controls subsequent behavior.
Basic Output Fields
| Field | Type | Description |
|---|---|---|
continue / continue_ | boolean | Whether to continue execution (default true) |
decision | 'block' | Set to 'block' to prevent operation |
reason | string | Reason for blocking |
stopReason | string | Stop message displayed when continue is false |
suppressOutput | boolean | Hide output |
PreToolUse Special Output
Can modify tool input:
typescript
return {
continue: true,
hookSpecificOutput: {
hookEventName: 'PreToolUse',
updatedInput: {
command: `echo "Security check passed" && ${input.command}`
}
}
};python
return {
"continue_": True,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": {
"command": f'echo "Security check passed" && {input_data["command"]}'
}
}
}Examples
Complete Example: Bash Command Auditing
typescript
import { query } from '@tencent-ai/agent-sdk';
import * as fs from 'fs';
const logFile = '/tmp/bash-audit.log';
const q = query({
prompt: 'Help me clean temporary files',
options: {
model: 'deepseek-v3.1',
hooks: {
PreToolUse: [{
matcher: 'Bash',
hooks: [
async (input, toolUseId) => {
const command = input.command as string;
const timestamp = new Date().toISOString();
// Log command
fs.appendFileSync(logFile, `${timestamp} [PRE] ${command}\n`);
// Dangerous command check
const dangerous = ['rm -rf /', 'mkfs', ':(){:|:&};:'];
for (const d of dangerous) {
if (command.includes(d)) {
return {
decision: 'block',
reason: `Dangerous command blocked: ${d}`
};
}
}
return { continue: true };
}
]
}],
PostToolUse: [{
matcher: 'Bash',
hooks: [
async (input, toolUseId) => {
const command = input.command as string;
const timestamp = new Date().toISOString();
// Log completion
fs.appendFileSync(logFile, `${timestamp} [POST] ${command} - Complete\n`);
return { continue: true };
}
]
}]
}
}
});
for await (const message of q) {
console.log(message);
}python
import asyncio
from datetime import datetime
from codebuddy_agent_sdk import query, CodeBuddyAgentOptions, HookMatcher
log_file = "/tmp/bash-audit.log"
async def pre_bash_hook(input_data, tool_use_id, context):
command = input_data.get("command", "")
timestamp = datetime.now().isoformat()
# Log command
with open(log_file, "a") as f:
f.write(f"{timestamp} [PRE] {command}\n")
# Dangerous command check
dangerous = ["rm -rf /", "mkfs", ":(){:|:&};:"]
for d in dangerous:
if d in command:
return {
"decision": "block",
"reason": f"Dangerous command blocked: {d}"
}
return {"continue_": True}
async def post_bash_hook(input_data, tool_use_id, context):
command = input_data.get("command", "")
timestamp = datetime.now().isoformat()
# Log completion
with open(log_file, "a") as f:
f.write(f"{timestamp} [POST] {command} - Complete\n")
return {"continue_": True}
async def main():
options = CodeBuddyAgentOptions(
model="deepseek-v3.1",
hooks={
"PreToolUse": [
HookMatcher(matcher="Bash", hooks=[pre_bash_hook])
],
"PostToolUse": [
HookMatcher(matcher="Bash", hooks=[post_bash_hook])
]
}
)
async for message in query(prompt="Help me clean temporary files", options=options):
print(message)
asyncio.run(main())Example: Limiting File Modification Scope
typescript
hooks: {
PreToolUse: [{
matcher: 'Write|Edit',
hooks: [
async (input) => {
const filePath = input.file_path as string;
// Only allow modifying src directory
if (!filePath.startsWith('/path/to/project/src/')) {
return {
decision: 'block',
reason: `Not allowed to modify files outside src directory: ${filePath}`
};
}
// Prohibit modifying config files
if (filePath.endsWith('.env') || filePath.includes('.git/')) {
return {
decision: 'block',
reason: 'Not allowed to modify sensitive files'
};
}
return { continue: true };
}
]
}]
}python
async def file_scope_hook(input_data, tool_use_id, context):
file_path = input_data.get("file_path", "")
# Only allow modifying src directory
if not file_path.startswith("/path/to/project/src/"):
return {
"decision": "block",
"reason": f"Not allowed to modify files outside src directory: {file_path}"
}
# Prohibit modifying config files
if file_path.endswith(".env") or ".git/" in file_path:
return {
"decision": "block",
"reason": "Not allowed to modify sensitive files"
}
return {"continue_": True}
hooks = {
"PreToolUse": [
HookMatcher(matcher="Write|Edit", hooks=[file_scope_hook])
]
}Related Documentation
- SDK Overview - Quick start and usage examples
- SDK Permission Control - canUseTool callback
- Hook Reference Guide - Complete CLI Hook reference
- TypeScript SDK Reference - Complete API reference
- Python SDK Reference - Complete API reference