docs/customization/hooks.mdx
Hooks are scripts that run at key moments in Cline's workflow. Because they execute at known points with consistent inputs and outputs, hooks bring determinism to the non-deterministic nature of AI models by enforcing guardrails, validations, and context injection. You can validate operations before they execute, monitor tool usage, and shape how Cline makes decisions.
.js files in a TypeScript project)Cline supports 8 hook types that run at different points in the task lifecycle:
| Hook Type | When It Runs |
|---|---|
| TaskStart | When you start a new task |
| TaskResume | When you resume an interrupted task |
| TaskCancel | When you cancel a running task |
| TaskComplete | When a task finishes successfully |
| PreToolUse | Before Cline executes a tool (read_file, write_to_file, etc.) |
| PostToolUse | After a tool execution completes |
| UserPromptSubmit | When you submit a message to Cline |
| PreCompact | Before Cline truncates conversation history to free up context |
flowchart TD
%% Styling
classDef hook fill:#FFB74D,stroke:#E65100,stroke-width:2px,color:black,rx:5,ry:5;
classDef state fill:#E1F5FE,stroke:#0277BD,stroke-width:2px,color:black;
classDef action fill:#FFFFFF,stroke:#333,stroke-width:1px,color:black,stroke-dasharray: 5 5;
%% Entry Points
Start((Start)) --> CheckType{New or
Resume?}
%% Initialization Hooks
CheckType -- New Task --> H_Start[TaskStart]:::hook
CheckType -- Resume --> H_Resume[TaskResume]:::hook
%% Main Loop
H_Start --> Loop(Task Active Loop):::state
H_Resume --> Loop
subgraph Conversation Cycle
direction TB
Loop -- User sends message --> H_Submit[UserPromptSubmit]:::hook
H_Submit --> Thinking[Cline Processes Context]:::state
%% Context Compaction Path
Thinking -. Context Limit Reached .-> H_Compact[PreCompact]:::hook
H_Compact -.-> Thinking
%% Tool Execution Path
Thinking -- Decides to use tool --> H_PreTool[PreToolUse]:::hook
H_PreTool -- Allowed --> ToolExec[Tool Executes]:::action
H_PreTool -- Cancelled --> Thinking
ToolExec --> H_PostTool[PostToolUse]:::hook
H_PostTool --> Thinking
end
%% Termination Paths
Thinking -- Task Successfully Finished --> H_Complete[TaskComplete]:::hook
Loop -- User Cancels Task --> H_Cancel[TaskCancel]:::hook
%% End
H_Complete --> End((End))
H_Cancel --> End
The diagram shows the complete hook lifecycle:
Orange nodes represent hooks where you can inject custom logic. The cycle repeats as you continue the conversation.
Hooks can be stored globally or in a project workspace. See Storage Locations for guidance on when to use each.
~/Documents/Cline/Hooks/.clinerules/hooks/ in your repo (can be committed to version control)When both global and workspace hooks exist for the same hook type, both run. Global hooks execute first, then workspace hooks. If either returns cancel: true, the operation stops.
Let's create a simple hook that logs every file Cline reads or writes. You'll see results in seconds.
Create a file called file-logger in your hooks directory with this content:
#!/bin/bash
# Logs all file operations to ~/cline-activity.log
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.preToolUse.toolName')
FILE_PATH=$(echo "$INPUT" | jq -r '.preToolUse.parameters.path // "N/A"')
# Log to file
echo "$(date '+%H:%M:%S') - $TOOL: $FILE_PATH" >> ~/cline-activity.log
# Always allow the operation
echo '{"cancel":false}'
HookName.ps1 is supported (PowerShell script files)HookName is supported (executable files like bash scripts or binaries)Wrong-platform naming is ignored by hook discovery. </Note>
Ask Cline to read any file in your project: "What's in package.json?"
Then check the log:
cat ~/cline-activity.log
You'll see entries like:
14:23:45 - read_file: /path/to/package.json
14:23:47 - search_files: /path/to/src
Try modifying the hook to:
.ts files)The sections below explain how hooks receive input and return output, plus more examples.
Hooks are executable scripts that receive JSON input via stdin and return JSON output via stdout.
Every hook receives a JSON object with common fields plus hook-specific data:
{
"taskId": "abc123",
"hookName": "PreToolUse",
"clineVersion": "3.17.0",
"timestamp": "1736654400000",
"workspaceRoots": ["/path/to/project"],
"userId": "user_123",
"model": {
"provider": "openrouter",
"slug": "anthropic/claude-sonnet-4.5"
},
// Hook-specific field (name matches hook type in camelCase)
"taskStart": {
"taskMetadata": {
"taskId": "abc123",
"ulid": "01J...",
"initialTask": "Add authentication to the API"
}
}
}
model.provider and model.slug are machine-stable identifiers for the active provider/model at hook execution time. If unavailable, Cline sends deterministic fallback values: "unknown".
timestamp is a string (milliseconds since epoch), not a numberworkspaceRoots is an array of workspace root paths and replaces the old singular workspacePathIf your scripts previously read .workspacePath, switch to .workspaceRoots[0] (or iterate all roots).
</Note>
The hook-specific field name matches the hook type:
taskStart, taskResume, taskCancel, taskComplete contain { taskMetadata: { taskId, ulid, ... } }preToolUse contains { toolName: string, parameters: object }postToolUse contains { toolName: string, parameters: object, result: string, success: boolean, executionTimeMs: number }userPromptSubmit contains { prompt: string, attachments: string[] }preCompact contains { taskId, ulid, contextSize, compactionStrategy, tokensIn, tokensOut, ... }Hooks return a JSON object to stdout:
{
"cancel": false,
"contextModification": "Optional text to add to the conversation",
"errorMessage": ""
}
| Field | Type | Description |
|---|---|---|
cancel | boolean | If true, stops the operation (blocks the tool, cancels the task start, etc.) |
contextModification | string | Optional text that gets injected into the conversation as context for Cline |
errorMessage | string | Shown to the user if cancel is true |
The contextModification field lets hooks inject information into the conversation. This is useful for:
For example, a PreToolUse hook could add: "Note: This file is auto-generated. Edits may be overwritten."
Runs when you start a new task. Use it to:
#!/bin/bash
INPUT=$(cat)
TASK=$(echo "$INPUT" | jq -r '.taskStart.taskMetadata.initialTask')
echo "[TaskStart] Starting: $TASK" >&2
echo '{"cancel":false,"contextModification":"","errorMessage":""}'
Runs when you resume an interrupted task (instead of TaskStart). Use it to:
Runs when you cancel a running task. Use it to:
Runs when a task completes successfully. Use it to:
Runs before any tool executes. This is the most powerful hook for validation and safety. Use it to:
The input includes the tool name and its parameters:
{
"preToolUse": {
"toolName": "write_to_file",
"parameters": {
"path": "src/config.ts",
"content": "..."
}
}
}
Example that blocks .js files in a TypeScript project:
#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.preToolUse.toolName')
FILE_PATH=$(echo "$INPUT" | jq -r '.preToolUse.parameters.path // empty')
if [[ "$TOOL" == "write_to_file" && "$FILE_PATH" == *.js ]]; then
echo '{"cancel":true,"errorMessage":"Use .ts files instead of .js in this TypeScript project"}'
exit 0
fi
echo '{"cancel":false}'
Runs after a tool completes (success or failure). Use it to:
The input includes execution results:
{
"postToolUse": {
"toolName": "execute_command",
"parameters": { "command": "npm test" },
"result": "All tests passed",
"success": true,
"executionTimeMs": 3450
}
}
Runs when you send a message to Cline. Use it to:
Runs before Cline truncates conversation history to stay within context limits. Use it to:
The input includes context metrics:
{
"preCompact": {
"taskId": "abc123",
"ulid": "01J...",
"contextSize": 45,
"compactionStrategy": "auto-condense",
"tokensIn": 125000,
"tokensOut": 8500,
"tokensInCache": 0,
"tokensOutCache": 0
}
}
Block creation of .js files in a TypeScript project:
#!/bin/bash
# PreToolUse hook
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.preToolUse.toolName')
FILE_PATH=$(echo "$INPUT" | jq -r '.preToolUse.parameters.path // empty')
if [[ "$TOOL" == "write_to_file" && "$FILE_PATH" == *.js ]]; then
echo '{"cancel":true,"errorMessage":"Use .ts files instead of .js in this TypeScript project"}'
exit 0
fi
echo '{"cancel":false}'
Log all tool executions to a file:
#!/bin/bash
# PostToolUse hook
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.postToolUse.toolName')
SUCCESS=$(echo "$INPUT" | jq -r '.postToolUse.success')
DURATION=$(echo "$INPUT" | jq -r '.postToolUse.executionTimeMs')
echo "$(date -Iseconds) | $TOOL | success=$SUCCESS | ${DURATION}ms" >> ~/.cline-tool-log.txt
echo '{"cancel":false}'
Inject project-specific information when a task begins:
#!/bin/bash
# TaskStart hook
INPUT=$(cat)
WORKSPACE=$(echo "$INPUT" | jq -r '.workspaceRoots[0] // empty')
# Read project info if available
if [[ -f "$WORKSPACE/.project-context" ]]; then
CONTEXT=$(cat "$WORKSPACE/.project-context")
echo "{\"cancel\":false,\"contextModification\":\"Project context: $CONTEXT\"}"
else
echo '{"cancel":false}'
fi
Hooks are available in the Cline CLI:
# Enable hooks for a task
cline "What does this repo do?" -s hooks_enabled=true
# Configure hooks globally
cline config set hooks-enabled=true
Hook not running?
chmod +x hookname)powershell -NoProfile -Command "$PSVersionTable.PSVersion")<HookName>.ps1 (for example PreToolUse.ps1)<HookName> naming (for example PreToolUse)Hook output not parsed?
>&2) for debug logging, not stdoutHook blocking unexpectedly?