.clinerules/hooks/README.md
Cline hooks allow you to execute custom scripts at specific points in the agentic workflow. Hooks can be placed in either:
~/Documents/Cline/Hooks/ (applies to all workspaces).clinerules/hooks/ (applies to the workspace the repo is part of)Hooks run automatically when enabled.
chmod +x hookname)~/Documents/Cline/Hooks/TaskStart.clinerules/hooks/TaskStart~/Documents/Cline/Hooks/TaskResume.clinerules/hooks/TaskResume~/Documents/Cline/Hooks/TaskCancel.clinerules/hooks/TaskCancel~/Documents/Cline/Hooks/TaskComplete.clinerules/hooks/TaskComplete~/Documents/Cline/Hooks/UserPromptSubmit.clinerules/hooks/UserPromptSubmit~/Documents/Cline/Hooks/PreToolUse.clinerules/hooks/PreToolUse~/Documents/Cline/Hooks/PostToolUse.clinerules/hooks/PostToolUse~/Documents/Cline/Hooks/PreCompact.clinerules/hooks/PreCompactCline uses a git-style approach for hooks that works consistently across all platforms:
PreToolUse or PostToolUse (no .bat, .cmd, .sh etc.)#!/usr/bin/env bash or #!/usr/bin/env node)chmod +x PreToolUseLike git hooks, Cline executes hook files through a shell that interprets the shebang line:
This means:
On Unix/Linux/macOS:
# Create hook file
nano ~/Documents/Cline/Hooks/PreToolUse
# Make executable
chmod +x ~/Documents/Cline/Hooks/PreToolUse
IMPORTANT: Context injected by hooks affects FUTURE AI decisions, not the current tool execution.
When a hook runs:
1. AI decides: "I'll use write_to_file with these parameters"
2. PreToolUse hook runs → can block or add context
3. If allowed, tool executes with original parameters
4. Context is added to conversation
5. Next API request includes this context
6. AI adjusts future decisions based on context
1. Tool completes execution
2. PostToolUse hook runs → observes results
3. Hook adds context about the outcome
4. Context is added to conversation
5. Next API request includes this context
6. AI can learn from the results
All hooks receive:
{
"clineVersion": "string",
"hookName": "TaskStart" | "TaskResume" | "TaskCancel" | "TaskComplete" | "UserPromptSubmit" | "PreToolUse" | "PostToolUse" | "PreCompact",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskStart": { // Only for TaskStart
"taskMetadata": {
"taskId": "string",
"ulid": "string",
"initialTask": "string"
}
},
"taskResume": { // Only for TaskResume
"taskMetadata": {
"taskId": "string",
"ulid": "string"
},
"previousState": {
"lastMessageTs": "string",
"messageCount": "string",
"conversationHistoryDeleted": "string"
}
},
"taskCancel": { // Only for TaskCancel
"taskMetadata": {
"taskId": "string",
"ulid": "string",
"completionStatus": "string"
}
},
"taskComplete": { // Only for TaskComplete
"taskMetadata": {
"taskId": "string",
"ulid": "string"
}
},
"userPromptSubmit": { // Only for UserPromptSubmit
"prompt": "string",
"attachments": ["string"]
},
"preToolUse": { // Only for PreToolUse
"toolName": "string",
"parameters": {}
},
"postToolUse": { // Only for PostToolUse
"toolName": "string",
"parameters": {},
"result": "string",
"success": boolean,
"executionTimeMs": number
},
"preCompact": { // Only for PreCompact
"contextSize": number,
"messagesToCompact": number,
"compactionStrategy": "string"
}
}
All hooks must return:
{
"cancel": boolean, // Required: false to continue, true to block execution
"contextModification": "string", // Optional: Context for future AI decisions
"errorMessage": "string" // Optional: Error details if blocking
}
Note: The cancel field works as follows:
false (or omitted): Allow execution to continuetrue: Block execution and show error message to userHOOK_EXECUTION_TIMEOUT_MS)MAX_CONTEXT_MODIFICATION_SIZE)#!/usr/bin/env bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
cat <<EOF
{
"cancel": true,
"errorMessage": "Cannot create .js files in TypeScript project",
"contextModification": "Use .ts/.tsx extensions only"
}
EOF
exit 0
fi
echo '{"cancel": false}'
#!/usr/bin/env bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
success=$(echo "$input" | jq -r '.postToolUse.success')
path=$(echo "$input" | jq -r '.postToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
cat <<EOF
{
"cancel": false,
"contextModification": "Created '$path'. Maintain consistency with this file's patterns in future operations."
}
EOF
else
echo '{"cancel": false}'
fi
#!/usr/bin/env bash
input=$(cat)
execution_time=$(echo "$input" | jq -r '.postToolUse.executionTimeMs')
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
if [[ "$execution_time" -gt 5000 ]]; then
cat <<EOF
{
"cancel": false,
"contextModification": "Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
}
EOF
else
echo '{"cancel": false}'
fi
#!/usr/bin/env bash
input=$(cat)
# Log to file
echo "$input" >> ~/.cline/hook-logs/tool-usage.jsonl
# Allow execution
echo '{"cancel": false}'
Cline supports two levels of hooks:
~/Documents/Cline/Hooks/ (macOS/Linux).clinerules/hooks/ in each workspace rootWhen multiple hooks exist (global and/or workspace):
Promise.allcancel: false), the tool proceedscancel: true), execution is blockedResult Combination:
cancel: If ANY hook returns true, execution is blockedcontextModification: All context strings are concatenated with double newlines (\n\n)errorMessage: All error messages are concatenated with single newlines (\n)The global hooks directory is automatically created at:
~/Documents/Cline/Hooks/Add your hook script:
# Unix/Linux/macOS
nano ~/Documents/Cline/Hooks/PreToolUse
chmod +x ~/Documents/Cline/Hooks/PreToolUse
Enable hooks in Cline settings
Global Hook (applies to all projects):
#!/usr/bin/env bash
# ~/Documents/Cline/Hooks/PreToolUse
# Universal rule: Never delete package.json
input=$(cat)
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$path" == *"package.json"* ]]; then
echo '{"cancel": true, "errorMessage": "Global policy: Cannot modify package.json"}'
exit 0
fi
echo '{"cancel": false}'
Workspace Hook (applies to specific project):
#!/usr/bin/env bash
# .clinerules/hooks/PreToolUse
# Project rule: Only TypeScript files
input=$(cat)
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
echo '{"cancel": true, "errorMessage": "Project rule: Use .ts files only"}'
exit 0
fi
echo '{"cancel": false}'
All hooks must allow execution for the tool to proceed. Hooks may execute concurrently.
If you have multiple workspace roots, you can place hooks in each root's .clinerules/hooks/ directory. All hooks (global and workspace) may execute concurrently. Their results will be combined:
true, execution is blockedNote: No execution order is guaranteed between hooks from different directories.
chmod +x hookname).gitignore to avoid committing sensitive hook logic