sdk/examples/hooks/README.md
Examples for file-based hooks and runtime hooks.
Use these terms consistently:
beforeRun, beforeModel, and afterTool.agent_end, tool_call, and prompt_submit.File hooks are an adapter on top of the runtime hook layer. Core discovers hook files, maps their event names onto runtime hook callbacks, then executes the matching script with a JSON payload on stdin.
| File Hook File Name | File Hook Event | Runtime Hook Backing It |
|---|---|---|
TaskStart | agent_start | beforeRun |
TaskResume | agent_resume | beforeRun with resume context |
UserPromptSubmit | prompt_submit | beforeRun plus submitted prompt context |
PreToolUse | tool_call | beforeTool |
PostToolUse | tool_result | afterTool |
TaskComplete | agent_end | afterRun when completed |
TaskError | agent_error | afterRun when failed |
TaskCancel | agent_abort | afterRun or session shutdown with abort/cancel reason |
SessionShutdown | session_shutdown | session cleanup / runtime shutdown |
PreCompact | not wired for file hooks today | none |
Use file hooks when you want a workspace or user-configured shell/Python script. Use runtime hooks when writing a plugin and needing typed, in-process access to runtime state or wanting to influence model/tool execution.
beforeRun and afterRun wrap one runtime run() or continue() invocation. In an interactive session, that means one submitted user turn. afterRun fires for completed, aborted, and failed runs; check result.status if you only want successful task completion.
For file hooks, successful task completion maps to the agent_end event. For a plugin, use afterRun and check result.status === "completed".
PreToolUse.shLog every tool call and its inputs. Useful for auditing what the agent is about to do.
mkdir -p .cline/hooks
cp examples/hooks/PreToolUse.sh .cline/hooks/
chmod +x .cline/hooks/PreToolUse.sh
cline -i "do something" # See tool calls logged to stderr
PostToolUse.shInspect tool results and add supplementary context.
mkdir -p .cline/hooks
cp examples/hooks/PostToolUse.sh .cline/hooks/
chmod +x .cline/hooks/PostToolUse.sh
cline -i "do something" # See tool results logged and enriched
PreToolUse_BlockDestructive.shPrevent destructive operations like force pushes or bulk deletes.
mkdir -p .cline/hooks
cp examples/hooks/PreToolUse_BlockDestructive.sh .cline/hooks/PreToolUse.sh
chmod +x .cline/hooks/PreToolUse.sh
cline -i "clean up the repo" # Destructive operations will be blocked
PreToolUse_RequireReview.shRequire user review before certain operations (file writes to critical files).
mkdir -p .cline/hooks
cp examples/hooks/PreToolUse_RequireReview.sh .cline/hooks/PreToolUse.sh
chmod +x .cline/hooks/PreToolUse.sh
cline -i "update dependencies" # Critical file writes will pause for review
PreToolUse_InjectFileContext.shExtract and inject file context before tool execution (related test files, lock files, environment context).
mkdir -p .cline/hooks
cp examples/hooks/PreToolUse_InjectFileContext.sh .cline/hooks/PreToolUse.sh
chmod +x .cline/hooks/PreToolUse.sh
cline -i "review the configuration" # Related files will be mentioned automatically
TaskStart.sh, TaskComplete.sh, SessionShutdown.shTrack agent session lifecycle events (start, end, shutdown).
mkdir -p .cline/hooks
cp examples/hooks/TaskStart.sh .cline/hooks/
cp examples/hooks/TaskComplete.sh .cline/hooks/
cp examples/hooks/SessionShutdown.sh .cline/hooks/
chmod +x .cline/hooks/Task*.sh .cline/hooks/SessionShutdown.sh
cline -i "do something" # Session lifecycle will be logged
PreToolUse.pyPython-based hook to log and filter tool calls.
mkdir -p .cline/hooks
cp examples/hooks/PreToolUse.py .cline/hooks/
chmod +x .cline/hooks/PreToolUse.py
cline -i "do something" # Python hook will log tool calls
PostToolUse.pyPython-based post-tool-use hook for result enrichment.
mkdir -p .cline/hooks
cp examples/hooks/PostToolUse.py .cline/hooks/
chmod +x .cline/hooks/PostToolUse.py
cline -i "do something" # Python hook will enrich tool results
PreToolUse_InjectContext.pyPython-based context injection with file analysis (test files, config files, lock files, Node.js version, git branch).
mkdir -p .cline/hooks
cp examples/hooks/PreToolUse_InjectContext.py .cline/hooks/PreToolUse.py
chmod +x .cline/hooks/PreToolUse.py
cline -i "add a new feature" # Related files and environment will be injected
PreToolUse.tsTypeScript hook for advanced tool call filtering and logging.
mkdir -p .cline/hooks
cp examples/hooks/PreToolUse.ts .cline/hooks/
chmod +x .cline/hooks/PreToolUse.ts
cline -i "do something" # TypeScript hook will execute via bun
PostToolUse.tsTypeScript hook for post-execution actions.
mkdir -p .cline/hooks
cp examples/hooks/PostToolUse.ts .cline/hooks/
chmod +x .cline/hooks/PostToolUse.ts
cline -i "do something" # TypeScript hook will execute via bun
PreToolUse_ModifyInput.tsModify tool inputs before execution (normalize paths, add defaults, sanitize).
mkdir -p .cline/hooks
cp examples/hooks/PreToolUse_ModifyInput.ts .cline/hooks/PreToolUse.ts
chmod +x .cline/hooks/PreToolUse.ts
cline -i "install dependencies" # npm install will have --save-exact added automatically
File hooks go in .cline/hooks/ and must be named after the event they handle:
mkdir -p .cline/hooks
# Copy PreToolUse example (pick your language)
cp examples/hooks/PreToolUse.sh .cline/hooks/PreToolUse.sh # Bash
cp examples/hooks/PreToolUse.py .cline/hooks/PreToolUse.py # Python
cp examples/hooks/PreToolUse.ts .cline/hooks/PreToolUse.ts # TypeScript
# Copy PostToolUse example
cp examples/hooks/PostToolUse.sh .cline/hooks/PostToolUse.sh # Bash
cp examples/hooks/PostToolUse.py .cline/hooks/PostToolUse.py # Python
cp examples/hooks/PostToolUse.ts .cline/hooks/PostToolUse.ts # TypeScript
chmod +x .cline/hooks/PreToolUse.*
chmod +x .cline/hooks/PostToolUse.*
cline -i "test prompt"
# Or load from a custom hooks directory:
cline --hooks-dir ./my-hooks -i "test prompt"
All hooks receive a detailed JSON event on stdin and must return JSON on stdout.
PreToolUse event:
{
"hookName": "tool_call",
"clineVersion": "1.0.0",
"timestamp": "2026-01-15T10:30:00Z",
"taskId": "conv-123",
"workspaceRoots": ["/path/to/repo"],
"userId": "user",
"iteration": 1,
"tool_call": {
"id": "call-456",
"name": "read_files",
"input": {"filePath": "/path/to/file.ts"}
}
}
PostToolUse event:
{
"hookName": "tool_result",
"clineVersion": "1.0.0",
"timestamp": "2026-01-15T10:30:00Z",
"tool_result": {
"id": "call-456",
"name": "read_files",
"input": {"filePath": "/path/to/file.ts"},
"output": "file contents here",
"error": null,
"durationMs": 45
}
}
TaskStart and other lifecycle events:
{
"hookName": "agent_start",
"clineVersion": "1.0.0",
"timestamp": "2026-01-15T10:30:00Z",
"taskId": "conv-123",
"workspaceRoots": ["/path/to/repo"],
"userId": "user"
}
Return a JSON object from stdout. Empty {} means "do nothing."
Available fields:
| Field | Type | Effect | Event(s) |
|---|---|---|---|
cancel | boolean | Cancels the pending tool call | PreToolUse |
review | boolean | Pauses and prompts for user review | PreToolUse |
context | string | Injects context into agent's next turn | PreToolUse, PostToolUse |
errorMessage | string | Surfaces an error to the agent | PreToolUse |
overrideInput | object | Replaces tool input before execution | PreToolUse |
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
echo "Action: $tool" >&2
echo '{}'
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
if [ "$tool" = "run_commands" ]; then
branch=$(git branch --show-current 2>/dev/null)
echo "{\"context\": \"Current branch: $branch\"}"
else
echo '{}'
fi
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
file=$(echo "$input" | jq -r '.tool_call.input.filePath')
if [ "$tool" = "read_files" ] && [[ $file == ~/* ]]; then
normalized="${file/#\~/$HOME}"
echo "{\"overrideInput\": {\"filePath\": \"$normalized\"}}"
else
echo '{}'
fi
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
cmd=$(echo "$input" | jq -r '.tool_call.input.command // empty')
if [ "$tool" = "run_commands" ] && [[ $cmd =~ git\ push\ --force ]]; then
echo '{"cancel": true, "errorMessage": "Force push is blocked."}'
else
echo '{}'
fi
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
file=$(echo "$input" | jq -r '.tool_call.input.filePath // empty')
if ([ "$tool" = "editor" ] || [ "$tool" = "write_file" ]) && \
[[ $file =~ (package\.json|\.env|secrets|tsconfig) ]]; then
echo '{"review": true, "context": "This will modify a critical file"}'
else
echo '{}'
fi
#!/usr/bin/env python3
import sys
import json
event = json.load(sys.stdin)
tool_name = event.get("tool_call", {}).get("name", "")
tool_input = event.get("tool_call", {}).get("input", {})
if tool_name == "read_files":
file_path = tool_input.get("filePath", "")
if file_path.endswith(".test.ts"):
print(json.dumps({"context": "This is a test file"}))
else:
print(json.dumps({}))
else:
print(json.dumps({}))
#!/usr/bin/env bun
interface HookEvent {
tool_call: { name: string; input: Record<string, unknown> };
}
const event: HookEvent = JSON.parse(await Bun.stdin.text());
const toolName = event.tool_call.name;
if (toolName === "run_commands") {
const branch = await getGitBranch();
console.log(JSON.stringify({ context: `Branch: ${branch}` }));
} else {
console.log(JSON.stringify({}));
}
async function getGitBranch(): Promise<string> {
return "main";
}
cline --verbose "your prompt"
echo '{"tool_call": {"name": "read_files", "input": {"filePath": "test.ts"}}}' | .cline/hooks/PreToolUse.sh
.cline/hooks/PreToolUse.sh < input.json | jq .
File hooks observe lifecycle events. For more advanced use cases like message compaction, use a TypeScript runtime hook plugin:
mkdir -p .cline/plugins
cp examples/hooks/custom-compaction-hook.example.ts .cline/plugins/custom-compaction-hook.ts
cline -i "Search the codebase for dispatcher usage, then summarize it"
This example uses hooks.beforeModel to estimate request size and replace older middle history with a summary message before the provider request.
| Example | Extension Point | Message Shape | Best For |
|---|---|---|---|
custom-compaction-hook.example.ts (in .cline/plugins/) | hooks.beforeModel runtime hook | Agent runtime request messages with runtime parts such as tool-call, tool-result, reasoning, image, and file | Cases needing runtime-hook context, current runtime snapshot, or direct request mutation |
plugins/custom-compaction.ts | api.registerMessageBuilder() | SDK/provider-bound Message[] after runtime messages are converted for model delivery | Most reusable plugin-owned message rewrites and compaction policies |
Prefer registerMessageBuilder() for normal plugin-owned provider-message rewrites because it runs in the core message pipeline before the built-in provider-safety builder. Use beforeModel when the compaction logic needs runtime hook context or needs to inspect the exact runtime request object.
--yolo mode — use --act or --plan to enable themjq — JSON parsing is finicky; use jq for safe extraction.cline/hooks/--hooks-dir ./ci/hooks to load from elsewhere