Hook Examples
Examples for file-based hooks and runtime hooks.
Hook Terminology
Use these terms consistently:
- Runtime hooks: Typed in-process plugin/agent lifecycle callbacks such as
beforeRun,beforeModel, andafterTool. - File hooks: External scripts discovered from hook config directories and run with serialized JSON payloads.
- Hook events: Serialized payload names used by file hooks, such as
agent_end,tool_call, andprompt_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 Hooks vs Runtime Hooks
| 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".
📂 Examples in This Directory
Bash Examples
PreToolUse.sh
Log 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.sh
Inspect 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.sh
Prevent 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.sh
Require 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.sh
Extract 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.sh
Track 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
Python Examples
PreToolUse.py
Python-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.py
Python-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.py
Python-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
TypeScript Examples
PreToolUse.ts
TypeScript 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.ts
TypeScript 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.ts
Modify 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
Getting Started
1. Copy a hook to your project
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
2. Make it executable
chmod +x .cline/hooks/PreToolUse.*
chmod +x .cline/hooks/PostToolUse.*
3. Test it
cline -i "test prompt"
# Or load from a custom hooks directory:
cline --hooks-dir ./my-hooks -i "test prompt"
Hook Input/Output Format
All hooks receive a detailed JSON event on stdin and must return JSON on stdout.
Input
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"
}
Output
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 |
Common Patterns
Log and proceed (bash)
#!/usr/bin/env bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_call.name')
echo "Action: $tool" >&2
echo '{}'
Inject context into next turn
#!/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
Modify tool input before execution
#!/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
Block specific tools or commands
#!/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
Require review for sensitive files
#!/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
Python: Parse and manipulate JSON
#!/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({}))
TypeScript: Type-safe hook with async operations
#!/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";
}
Debugging Hooks
Print hook invocations
cline --verbose "your prompt"
Test a hook manually
echo '{"tool_call": {"name": "read_files", "input": {"filePath": "test.ts"}}}' | .cline/hooks/PreToolUse.sh
Check hook output
.cline/hooks/PreToolUse.sh < input.json | jq .
Runtime Hooks: Custom Compaction
File hooks observe lifecycle events. For more advanced use cases like message compaction, use a TypeScript runtime hook plugin:
cline plugin install https://github.com/cline/cline/blob/main/sdk/examples/hooks/custom-compaction-hook.example.ts --cwd .
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.
Runtime Hook vs Message-Builder Compaction
| 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.
Tips
- Hooks are disabled in
--yolomode — use--actor--planto enable them - Use stderr for logging — stdout is reserved for JSON output
- Keep hooks fast — they run before every tool call, so performance matters
- Test with
jq— JSON parsing is finicky; usejqfor safe extraction - Use multiple hooks — different event files can coexist in
.cline/hooks/ - Load from custom dirs — use
--hooks-dir ./ci/hooksto load from elsewhere