* docs: add enforced .clineignore guard plugin example Adds clineignore-read-files-guard.ts, a beforeTool hook plugin that blocks read_files, editor, apply_patch, and run_commands calls targeting paths matching gitignore-style patterns in a workspace .clineignore file, and protects .clineignore itself from modification. Features it on the .clineignore docs page as the enforced replacement for the deprecated built-in feature. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * docs: use a PreToolUse file hook for the enforced .clineignore example Replaces the plugin-based guard with a PreToolUse hook script that works in the VS Code extension today (.clinerules/hooks/PreToolUse plus the Enable Hooks setting) as well as the CLI (.cline/hooks/PreToolUse.sh). The script handles both hook payload shapes, blocks read_files/editor/apply_patch/ run_commands calls matching .clineignore patterns, and protects .clineignore itself from modification. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix: canonicalize paths in .clineignore guard hook Lexically collapse '.', '..', and empty segments before the ignore match and the .clineignore self-protection check, closing bypasses via noncanonical paths like ./.clineignore, secrets/../.env, or /root/./file (Greptile review finding on #13649). Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * docs: note symlink limitation in .clineignore guard hook docs Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> --------- Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
14 KiB
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_ClineignoreGuard.sh
Block file reads, edits, and shell commands whose paths match gitignore-style patterns in a workspace .clineignore file — an enforced replacement for the legacy .clineignore feature. Handles both the CLI and VS Code extension hook payload shapes, and protects .clineignore itself from modification. See the .clineignore docs for the full walkthrough.
mkdir -p .cline/hooks
cp examples/hooks/PreToolUse_ClineignoreGuard.sh .cline/hooks/PreToolUse.sh
chmod +x .cline/hooks/PreToolUse.sh
echo ".env" >> .clineignore
cline -i "read the .env file" # The read is cancelled before it runs
For the VS Code extension, copy it to .clinerules/hooks/PreToolUse (no file extension) instead and check "Enable Hooks" in Cline's feature settings.
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