tencent cloud

Hook System

Download
Focus Mode
Font Size
Last updated: 2026-09-30 18:41:48
AI-Translated
Version Requirements: This document applies to CodeBuddy Agent SDK v0.1.0 and later versions.
This document describes how to use the Hook system in the SDK to insert custom logic before and after tool execution.

Overview

Hooks allow you to insert custom logic within the CodeBuddy session lifecycle to achieve the following:
Validation and interception before tool invocation
Logging after tool execution
Review of user-submitted content
Initialization and cleanup at session start/end
Custom processes for worktree creation and cleanup

Supported Events

Event
Trigger Timing
PreToolUse
Before tool execution
PostToolUse
After tool execution succeeds
UserPromptSubmit
When a user submits a message
Stop
When the main Agent response ends
SubagentStop
When the sub-Agent ends
PreCompact
Before context compression
WorktreeCreate
When an isolated worktree is created
WorktreeRemove
When an isolated worktree is deleted
unstable_Checkpoint
When a checkpoint is automatically created after file modification

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
Python
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', // Matches only the Bash tool
hooks: [
async (input, toolUseId, ctx) => {
console.log('About to execute:', input);
return { continue: true };
}
],
timeout: 5000 // Timeout in milliseconds
}
]
}
}
});
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", # Matches only the 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
Matching mode, supports regular expressions. * or an empty string matches all.
hooks
HookCallback[]
Array of callback functions
timeout
number
Timeout (in milliseconds for TypeScript, in seconds for Python)

Matcher Mode

Exact match: "Bash" matches only the Bash tool.
Regular expression match: "Edit|Write" matches Edit or Write.
Wildcard: "*" or "" matches all tools.
Prefix match: "mcp__.*" matches all MCP tools.

Event type

PreToolUse

Triggered before tool execution, it can prevent execution or modify the input.
TypeScript
Python
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 };
}
]
}]
}
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, it can add additional context.
TypeScript
Python
hooks: {
PostToolUse: [{
matcher: 'Write|Edit',
hooks: [
async (input, toolUseId) => {
console.log(`File modified: ${input.file_path}`);
// Log the modification
await logFileChange(input.file_path);
return { continue: true };
}
]
}]
}
async def post_write_hook(input_data, tool_use_id, context):
print(f"File modified: {input_data.get('file_path')}")
# Log the modification
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 a user submits a message, it can add context or block processing.
TypeScript
Python
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 };
}
]
}]
}
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 the Agent response ends, it can prevent stopping and request continuation.
TypeScript
Python
hooks: {
Stop: [{
hooks: [
async (input) => {
// Check whether the task is actually complete
if (!isTaskComplete()) {
return {
decision: 'block',
reason: 'Task not completed, please continue'
};
}
return { continue: true };
}
]
}]
}
async def stop_hook(input_data, tool_use_id, context):
# Check whether the task is actually complete
if not is_task_complete():
return {
"decision": "block",
"reason": "Task not completed, please continue"
}
return {"continue_": True}

hooks = {
"Stop": [HookMatcher(hooks=[stop_hook])]
}

unstable_Checkpoint (Experimental)

Triggered automatically after a file is modified (when the Write/Edit/MultiEdit tool executes successfully), it provides file snapshots and change statistics.
Experimental APIs
This Hook is an experimental feature, and its API may change in future versions.
TypeScript
Python
import type { CheckpointHookInput } from '@tencent-ai/agent-sdk';

hooks: {
unstable_Checkpoint: [{
hooks: [
async (input) => {
const checkpointInput = input as CheckpointHookInput;
const checkpoint = checkpointInput.checkpoint;
console.log('File change checkpoint:', {
id: checkpoint.id,
label: checkpoint.label,
files: checkpoint.fileChangeStats?.files,
additions: checkpoint.fileChangeStats?.additions,
deletions: checkpoint.fileChangeStats?.deletions
});
// Access file snapshots
for (const [filePath, version] of Object.entries(checkpoint.fileSnapshots)) {
console.log(` ${filePath} - version ${version.version}`);
}
return { continue: true };
}
]
}]
}
async def checkpoint_hook(input_data, tool_use_id, context):
checkpoint = input_data.get("checkpoint", {})
file_change_stats = checkpoint.get("fileChangeStats", {})
print(f"File change checkpoint:")
print(f" ID: {checkpoint.get('id')}")
print(f" Label: {checkpoint.get('label')}")
print(f" Files: {file_change_stats.get('files', [])}")
print(f" Additions: +{file_change_stats.get('additions', 0)} lines")
print(f" Deletions: -{file_change_stats.get('deletions', 0)} lines")
# Access file snapshots
for file_path, version in checkpoint.get("fileSnapshots", {}).items():
print(f" {file_path} - version {version.get('version')}")
return {"continue_": True}

hooks = {
"unstable_Checkpoint": [HookMatcher(hooks=[checkpoint_hook])]
}
Checkpoint data structure:
id: Unique identifier of the checkpoint
label: A human-readable label (usually a user prompt)
createdAt: Creation timestamp
fileSnapshots: A mapping from file paths to version information
filePath: Absolute path of the file
version: Version number
backupFileName: Backup file name
backupTime: Backup timestamp
fileChangeStats: File change statistics
files: A list of changed file paths
additions: Number of added lines
deletions: Number of deleted lines

Hook Input

The input structure received by a Hook callback varies by event type.

Public Fields

{
"session_id": "abc123",
"cwd": "/path/to/project",
"permission_mode": "default",
"hook_event_name": "PreToolUse"
}

PreToolUse / PostToolUse Input

{
"tool_name": "Bash",
"tool_input": {
"command": "ls -la"
}
}

UserPromptSubmit Input

{
"prompt": "Help me write a function"
}

Stop / SubagentStop Input

{
"stop_hook_active": false
}
WorktreeCreate input example:
{
"hook_event_name": "WorktreeCreate",
"session_id": "abc123",
"cwd": "/path/to/project",
"transcript_path": "/path/to/transcript.jsonl",
"name": "feature-auth"
}
WorktreeRemove input example:
{
"hook_event_name": "WorktreeRemove",
"session_id": "abc123",
"cwd": "/path/to/project",
"transcript_path": "/path/to/transcript.jsonl",
"worktree_path": "/tmp/codebuddy-worktrees/feature-auth"
}

unstable_Checkpoint Input

{
"checkpoint": {
"id": "ckpt_abc123",
"label": "Help me write a function",
"createdAt": 1705920000000,
"fileSnapshots": {
"/path/to/file.ts": {
"filePath": "/path/to/file.ts",
"version": 1,
"backupFileName": "file.ts.v1.backup",
"backupTime": 1705920000000
}
},
"fileChangeStats": {
"files": ["/path/to/file.ts"],
"additions": 10,
"deletions": 2
}
}
}

Hook Output

The output returned by a Hook callback controls subsequent behavior.

Basic Output Fields

Field
Type
Description
continue / continue_
boolean
Whether to continue execution (default: true)
decision
'block'
Set to 'block' to block the operation.
reason
string
Reason for blocking
stopReason
string
Stop message displayed when continue is false
suppressOutput
boolean
Suppress output

PreToolUse Special Output

You can modify the tool input:
TypeScript
Python
return {
continue: true,
hookSpecificOutput: {
hookEventName: 'PreToolUse',
updatedInput: {
command: `echo "Security check passed" && ${input.command}`
}
}
};
return {
"continue_": True,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": {
"command": f'echo "Security check passed" && {input_data["command"]}'
}
}
}

PostToolUse Special Output

You can append context to the Agent (additionalContext), or use updatedToolOutput to replace the tool result that will be sent to the Agent (effective for all tools, commonly used to compress verbose output to save tokens):
TypeScript
Python
return {
continue: true,
hookSpecificOutput: {
hookEventName: 'PostToolUse',
// Replace the tool result (the result may become shorter). You can also use additionalContext to append information (the result will only become longer).
updatedToolOutput: compress(input.tool_response)
}
};
return {
"continue_": True,
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
# Replace the tool result (the result may become shorter). You can also use additionalContext to append information (the result will only become longer).
"updatedToolOutput": compress(input_data.get("tool_response"))
}
}

Examples

Complete Example: Bash Command Audit

TypeScript
Python
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 up 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 the command
fs.appendFileSync(logFile, `${timestamp} [PRE] ${command}\\n`);

// Check for dangerous commands
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 execution completion
fs.appendFileSync(logFile, `${timestamp} [POST] ${command} - Done\\n`);

return { continue: true };
}
]
}]
}
}
});

for await (const message of q) {
console.log(message);
}
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 the command
with open(log_file, "a") as f:
f.write(f"{timestamp} [PRE] {command}\\n")

# Check for dangerous commands
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 execution completion
with open(log_file, "a") as f:
f.write(f"{timestamp} [POST] {command} - Done\\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 up temporary files", options=options):
print(message)

asyncio.run(main())

Example: Restricting the Scope of File Modifications

TypeScript
Python
hooks: {
PreToolUse: [{
matcher: 'Write|Edit',
hooks: [
async (input) => {
const filePath = input.file_path as string;

// Only allow modifications to the src directory
if (!filePath.startsWith('/path/to/project/src/')) {
return {
decision: 'block',
reason: `Modifying files outside the src directory is not allowed: ${filePath}`
};
}

// Do not modify configuration files
if (filePath.endsWith('.env') || filePath.includes('.git/')) {
return {
decision: 'block',
reason: 'Modifying sensitive files is not allowed'
};
}

return { continue: true };
}
]
}]
}
async def file_scope_hook(input_data, tool_use_id, context):
file_path = input_data.get("file_path", "")

# Only allow modifications to the src directory
if not file_path.startswith("/path/to/project/src/"):
return {
"decision": "block",
"reason": f"Modifying files outside the src directory is not allowed: {file_path}"
}

# Do not modify configuration files
if file_path.endswith(".env") or ".git/" in file_path:
return {
"decision": "block",
"reason": "Modifying sensitive files is not allowed"
}

return {"continue_": True}

hooks = {
"PreToolUse": [
HookMatcher(matcher="Write|Edit", hooks=[file_scope_hook])
]
}

Example: File Modification Tracking (Checkpoint Hook)

TypeScript
Python
import { query, type CheckpointHookInput } from '@tencent-ai/agent-sdk';
import * as fs from 'fs';

const changeLog = '/tmp/file-changes.log';

const q = query({
prompt: 'Refactor the src/utils.ts file',
options: {
model: 'deepseek-v3.1',
hooks: {
unstable_Checkpoint: [{
hooks: [
async (input) => {
const checkpointInput = input as CheckpointHookInput;
const checkpoint = checkpointInput.checkpoint;
const stats = checkpoint.fileChangeStats;
if (!stats) return { continue: true };
// Log file changes
const timestamp = new Date().toISOString();
const logEntry = `
[${timestamp}] Checkpoint ${checkpoint.id}
Label: ${checkpoint.label}
Files: ${stats.files.join(', ')}
Changes: +${stats.additions}/-${stats.deletions}
Snapshots: ${Object.keys(checkpoint.fileSnapshots).length} files
`;
fs.appendFileSync(changeLog, logEntry);
// If the changes are too large, remind the user
if (stats.additions + stats.deletions > 100) {
console.warn('Large amount of code changes, review is recommended');
}
return { continue: true };
}
]
}]
}
}
});

for await (const message of q) {
console.log(message);
}
import asyncio
from datetime import datetime
from codebuddy_agent_sdk import query, CodeBuddyAgentOptions, HookMatcher

change_log = "/tmp/file-changes.log"

async def checkpoint_tracker(input_data, tool_use_id, context):
checkpoint = input_data.get("checkpoint", {})
stats = checkpoint.get("fileChangeStats")
if not stats:
return {"continue_": True}
# Log file changes
timestamp = datetime.now().isoformat()
log_entry = f"""
[{timestamp}] Checkpoint {checkpoint.get('id')}
Label: {checkpoint.get('label')}
Files: {', '.join(stats.get('files', []))}
Changes: +{stats.get('additions', 0)}/-{stats.get('deletions', 0)}
Snapshots: {len(checkpoint.get('fileSnapshots', {}))} files
"""
with open(change_log, "a") as f:
f.write(log_entry)
# If the changes are too large, remind the user
total_changes = stats.get("additions", 0) + stats.get("deletions", 0)
if total_changes > 100:
print("⚠️ Large amount of code changes, review is recommended")
return {"continue_": True}

async def main():
options = CodeBuddyAgentOptions(
model="deepseek-v3.1",
hooks={
"unstable_Checkpoint": [
HookMatcher(hooks=[checkpoint_tracker])
]
}
)
async for message in query(prompt="Refactor the src/utils.ts file", options=options):
print(message)

asyncio.run(main())

References

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

Help and Support

Was this page helpful?

Help us improve! Rate your documentation experience in 5 mins.

Feedback