Skip to main content
This page provides complete, production-ready hook examples organized by skill level. Each example includes full working code, detailed explanations, and guidance on when to use each pattern.

How to Use These Samples

Each sample is designed to be:
  • Copy-and-paste ready: Use them directly or as starting points
  • Educational: Learn hook concepts through progressive complexity
  • Practical: Solve real development workflow challenges
Choose samples based on your experience level and gradually work up to more advanced patterns.

Beginner Examples

Perfect for getting started with hooks. These examples demonstrate core concepts with straightforward logic.

1. Project Type Detection

Hook: TaskStart
#!/usr/bin/env bash
# Project Type Detection Hook
# 
# Overview: Automatically detects project type at task start and injects relevant
# coding standards and best practices into the AI context. This helps Cline understand
# your project structure and apply appropriate conventions from the beginning.
#
# Demonstrates: Basic hook input/output, file system checks, conditional logic,
# and context injection to guide AI behavior.

input=$(cat)

# Read basic JSON structure and detect project type
context=""

# Check for different project indicators
if [[ -f "package.json" ]]; then
  if grep -q "react" package.json; then
    context="PROJECT_TYPE: React application detected. Follow component-based architecture and use functional components."
  elif grep -q "express" package.json; then
    context="PROJECT_TYPE: Express.js API detected. Follow RESTful patterns and proper middleware structure."
  else
    context="PROJECT_TYPE: Node.js project detected. Use proper npm scripts and dependency management."
  fi
elif [[ -f "requirements.txt" ]] || [[ -f "pyproject.toml" ]]; then
  context="PROJECT_TYPE: Python project detected. Follow PEP 8 standards and use virtual environments."
elif [[ -f "Cargo.toml" ]]; then
  context="PROJECT_TYPE: Rust project detected. Follow Rust conventions and use proper error handling."
elif [[ -f "go.mod" ]]; then
  context="PROJECT_TYPE: Go project detected. Follow Go conventions and use proper package structure."
fi

# Return the context to guide Cline's behavior
if [[ -n "$context" ]]; then
  jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
else
  echo '{"cancel": false}'
fi
Key Concepts:
  • Reading hook input with input=$(cat)
  • Using file system checks to detect project type
  • Returning context to influence AI behavior
  • Basic JSON output with jq

2. File Extension Validator

Hook: PreToolUse
#!/usr/bin/env bash
# File Extension Validator Hook
#
# Overview: Enforces TypeScript file extensions in TypeScript projects by blocking
# creation of .js and .jsx files. This prevents common mistakes where developers
# accidentally create JavaScript files when they should be using TypeScript.
#
# Demonstrates: PreToolUse blocking, parameter extraction, conditional validation,
# and providing clear error messages to guide users toward correct file extensions.

input=$(cat)

# Extract tool information
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')

# Only process file creation tools
if [[ "$tool_name" != "write_to_file" ]]; then
  echo '{"cancel": false}'
  exit 0
fi

# Check if this is a TypeScript project
if [[ ! -f "tsconfig.json" ]]; then
  echo '{"cancel": false}'
  exit 0
fi

# Get the file path from tool parameters
file_path=$(echo "$input" | jq -r '.preToolUse.parameters.path // empty')

if [[ -z "$file_path" ]]; then
  echo '{"cancel": false}'
  exit 0
fi

# Block .js files in TypeScript projects
if [[ "$file_path" == *.js ]]; then
  echo '{"cancel": true, "errorMessage": "JavaScript files (.js) are not allowed in TypeScript projects. Use .ts extension instead."}'
  exit 0
fi

# Block .jsx files, suggest .tsx
if [[ "$file_path" == *.jsx ]]; then
  echo '{"cancel": true, "errorMessage": "JSX files (.jsx) are not allowed in TypeScript projects. Use .tsx extension instead."}'
  exit 0
fi

# Everything is OK
echo '{"cancel": false}'
Key Concepts:
  • Extracting tool name and parameters
  • Conditional logic based on project state
  • Blocking operations with "cancel": true
  • Providing helpful error messages

3. Basic Performance Monitor

Hook: PostToolUse
#!/usr/bin/env bash
# Basic Performance Monitor Hook
#
# Overview: Monitors tool execution times and logs operations that exceed a 3-second
# threshold. This helps identify performance bottlenecks and provides feedback to
# users about system resource issues that may be slowing down Cline's operations.
#
# Demonstrates: PostToolUse hook usage, arithmetic operations in bash, simple file
# logging, and conditional context injection based on performance metrics.

input=$(cat)

# Extract performance information
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
execution_time=$(echo "$input" | jq -r '.postToolUse.executionTimeMs // 0')
success=$(echo "$input" | jq -r '.postToolUse.success')

# Log slow operations (threshold: 3 seconds)
if (( execution_time > 3000 )); then
  # Create simple log directory
  mkdir -p "$HOME/.cline_logs"
  
  # Log the slow operation
  echo "$(date -Iseconds): SLOW OPERATION - $tool_name took ${execution_time}ms" >> "$HOME/.cline_logs/performance.log"
  
  # Provide feedback to user
  context="PERFORMANCE: Operation $tool_name took ${execution_time}ms. Consider checking system resources if this happens frequently."
  jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
else
  echo '{"cancel": false}'
fi
Key Concepts:
  • Processing results after tool execution
  • Basic arithmetic operations in bash
  • Simple file logging
  • Conditional context injection

Intermediate Examples

These examples demonstrate more advanced concepts including external tool integration, pattern matching, and structured logging.

4. Code Quality with Linting

Hook: PreToolUse
#!/usr/bin/env bash
# Code Quality Linting Hook
#
# Overview: Integrates ESLint and Flake8 to enforce code quality standards before
# files are written. Blocks file creation if linting errors are detected, ensuring
# all code meets quality standards. Supports TypeScript, JavaScript, and Python files.
#
# Demonstrates: External tool integration, temporary file handling, regex pattern
# matching, and comprehensive error reporting with actionable feedback.

input=$(cat)

tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')

# Only lint file write operations
if [[ "$tool_name" != "write_to_file" ]]; then
  echo '{"cancel": false}'
  exit 0
fi

file_path=$(echo "$input" | jq -r '.preToolUse.parameters.path // empty')

# Skip non-code files
if [[ ! "$file_path" =~ \.(ts|tsx|js|jsx|py|rs)$ ]]; then
  echo '{"cancel": false}'
  exit 0
fi

# Get file content from the tool parameters  
content=$(echo "$input" | jq -r '.preToolUse.parameters.content // empty')

if [[ -z "$content" ]]; then
  echo '{"cancel": false}'
  exit 0
fi

# Create temporary file for linting
temp_file=$(mktemp)
echo "$content" > "$temp_file"

# Run appropriate linter based on file extension
lint_errors=""
if [[ "$file_path" =~ \.(ts|tsx)$ ]] && command -v eslint > /dev/null; then
  lint_output=$(eslint "$temp_file" --format=json 2>/dev/null || true)
  if [[ "$lint_output" != "[]" ]] && [[ -n "$lint_output" ]]; then
    error_count=$(echo "$lint_output" | jq '.[0].errorCount // 0')
    if (( error_count > 0 )); then
      messages=$(echo "$lint_output" | jq -r '.[0].messages[] | "\(.line):\(.column) \(.message)"')
      lint_errors="ESLint errors found:\n$messages"
    fi
  fi
elif [[ "$file_path" =~ \.py$ ]] && command -v flake8 > /dev/null; then
  lint_output=$(flake8 "$temp_file" 2>/dev/null || true)
  if [[ -n "$lint_output" ]]; then
    lint_errors="Flake8 errors found:\n$lint_output"
  fi
fi

# Cleanup
rm -f "$temp_file"

# Block if linting errors found
if [[ -n "$lint_errors" ]]; then
  error_message="Code quality check failed. Please fix these issues:\n\n$lint_errors"
  jq -n --arg msg "$error_message" '{"cancel": true, "errorMessage": $msg}'
else
  echo '{"cancel": false}'
fi
Key Concepts:
  • Temporary file creation and cleanup
  • External tool integration (eslint, flake8)
  • Complex pattern matching with regex
  • Structured error reporting

5. Security Scanner

Hook: PreToolUse
#!/usr/bin/env bash
# Security Scanner Hook
#
# Overview: Scans file content for hardcoded secrets (API keys, tokens, passwords)
# before files are written. Blocks creation of files containing secrets except in
# safe locations like .env.example files or documentation, preventing credential leaks.
#
# Demonstrates: Pattern matching with regex arrays, file path exception handling,
# security-focused validation, and clear user guidance in error messages.

input=$(cat)

tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')

# Only check file operations
if [[ "$tool_name" != "write_to_file" ]]; then
  echo '{"cancel": false}'
  exit 0
fi

content=$(echo "$input" | jq -r '.preToolUse.parameters.content // empty')
file_path=$(echo "$input" | jq -r '.preToolUse.parameters.path // empty')

# Skip if no content
if [[ -z "$content" ]]; then
  echo '{"cancel": false}'
  exit 0
fi

# Define secret patterns (simplified for readability)
secrets_found=""

# Check for API keys
if echo "$content" | grep -qi "api[_-]*key.*[=:].*['\"][a-z0-9_-]{10,}['\"]"; then
  secrets_found+="- API key pattern detected\n"
fi

# Check for tokens
if echo "$content" | grep -qi "token.*[=:].*['\"][a-z0-9_-]{10,}['\"]"; then
  secrets_found+="- Token pattern detected\n"
fi

# Check for passwords
if echo "$content" | grep -qi "password.*[=:].*['\"][^'\"]{8,}['\"]"; then
  secrets_found+="- Password pattern detected\n"
fi

# Allow secrets in safe files
safe_patterns=("\.env\.example$" "\.env\.template$" "/docs/" "\.md$")
is_safe_file=false
for safe_pattern in "${safe_patterns[@]}"; do
  if [[ "$file_path" =~ $safe_pattern ]]; then
    is_safe_file=true
    break
  fi
done

if [[ -n "$secrets_found" ]] && [[ "$is_safe_file" == false ]]; then
  error_message="🔒 SECURITY ALERT: Potential secrets detected in $file_path

$secrets_found
Please use environment variables or a secrets management service instead."

  jq -n --arg msg "$error_message" '{"cancel": true, "errorMessage": $msg}'
else
  echo '{"cancel": false}'
fi
Key Concepts:
  • Pattern arrays and iteration
  • File path exception handling
  • Security-focused validation
  • Clear user guidance in error messages

6. Git Workflow Assistant

Hook: PostToolUse
#!/usr/bin/env bash
# Git Workflow Assistant Hook
#
# Overview: Analyzes file modifications and provides intelligent git workflow suggestions
# based on file types and current branch. Encourages best practices like feature branches
# for components and test branches for test files, with actionable git commands.
#
# Demonstrates: Git integration, branch analysis, file path pattern matching, and
# contextual suggestions to guide users toward better git practices.

input=$(cat)

tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
success=$(echo "$input" | jq -r '.postToolUse.success')

# Only process successful file modifications
if [[ "$success" != "true" ]] || [[ "$tool_name" != "write_to_file" && "$tool_name" != "replace_in_file" ]]; then
  echo '{"cancel": false}'
  exit 0
fi

# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
  echo '{"cancel": false}'
  exit 0
fi

file_path=$(echo "$input" | jq -r '.postToolUse.parameters.path // empty')
current_branch=$(git branch --show-current 2>/dev/null || echo "main")

# Analyze file type and suggest appropriate branch naming
context=""
if [[ "$file_path" == *"component"* ]] && [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
  component_name=$(basename "$file_path" .tsx .ts .jsx .js)
  context="GIT_WORKFLOW: Consider creating a feature branch: git checkout -b feature/add-${component_name,,}-component"
elif [[ "$file_path" == *"test"* ]] || [[ "$file_path" == *"spec"* ]]; then
  if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
    context="GIT_WORKFLOW: Consider creating a test branch: git checkout -b test/add-tests-$(basename "$(dirname "$file_path")")"
  fi
fi

# Add staging guidance
if [[ -n "$context" ]]; then
  context="$context After completing changes, use 'git add $file_path' to stage for commit."
else
  context="GIT_WORKFLOW: File modified: $file_path. Use 'git add $file_path' when ready to commit."
fi

jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
Key Concepts:
  • Git repository detection
  • Branch analysis and suggestions
  • File path analysis for context
  • Actionable user guidance

Advanced Examples

These examples showcase sophisticated patterns including external integrations, asynchronous processing, and complex state management.

7. Comprehensive Task Lifecycle Manager

Hook: TaskComplete
#!/usr/bin/env bash
# Comprehensive Task Lifecycle Manager Hook
#
# Overview: Tracks task completions by generating detailed markdown reports with
# workspace information and git state, and optionally sends webhook notifications
# to external systems. Perfect for enterprise environments requiring audit trails.
#
# Demonstrates: Complex data extraction, structured report generation, markdown
# heredocs, asynchronous webhook notifications, and robust error handling.

input=$(cat)

# Extract task metadata using proper API field paths
task_id=$(echo "$input" | jq -r '.taskId')
ulid=$(echo "$input" | jq -r '.taskComplete.taskMetadata.ulid // "unknown"')
completion_time=$(echo "$input" | jq -r '.timestamp')

# Create completion report directory with error handling
reports_dir="$HOME/.cline_reports"
if [[ ! -d "$(dirname "$reports_dir")" ]]; then
  echo '{"cancel": false, "errorMessage": "Cannot access home directory"}' 
  exit 0
fi
mkdir -p "$reports_dir" || exit 0

# Generate safe, unique report filename
safe_task_id=$(echo "$task_id" | tr -cd '[:alnum:]_-' | head -c 50)
report_file="$reports_dir/completion_$(date +%Y%m%d_%H%M%S)_${safe_task_id}.md"

# Collect comprehensive workspace information
git_branch=$(git branch --show-current 2>/dev/null || echo "No git repository")
git_status_count=$(git status --porcelain 2>/dev/null | wc -l || echo "0")
project_name=$(basename "$PWD")

# Generate detailed completion report
cat > "$report_file" << EOF
# Cline Task Completion Report

**Task ID:** $task_id  
**ULID:** $ulid  
**Completed:** $(date -Iseconds)  
**Completion Time:** $completion_time

## Workspace Information
- **Project:** $project_name
- **Git Branch:** $git_branch
- **Modified Files:** $git_status_count

## Completion Status
✅ Task completed successfully

## Next Steps
- Review changes made during this task
- Consider committing changes if appropriate  
- Run tests to verify functionality
EOF

# Send webhook notification if configured
webhook_url="${COMPLETION_WEBHOOK_URL:-}"
if [[ -n "$webhook_url" ]]; then
  payload=$(jq -n \
    --arg task_id "$task_id" \
    --arg ulid "$ulid" \
    --arg workspace "$project_name" \
    --arg timestamp "$completion_time" \
    '{
      event: "task_completed",
      task_id: $task_id,
      ulid: $ulid,
      workspace: $workspace,
      timestamp: $timestamp
    }')
  
  # Send notification in background with timeout
  (curl -X POST \
    -H "Content-Type: application/json" \
    -d "$payload" \
    "$webhook_url" \
    --max-time 5 \
    --silent > /dev/null 2>&1) &
fi

context="TASK_COMPLETED: ✅ Task $task_id finished successfully. Report saved to: $(basename "$report_file")"
jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
Key Concepts:
  • Complex data extraction and validation
  • Structured report generation
  • Asynchronous webhook notifications
  • Error handling and resource management

8. Intelligent User Input Enhancer

Hook: UserPromptSubmit
#!/usr/bin/env bash
# Intelligent User Input Enhancer Hook
#
# Overview: Analyzes user prompts to detect potentially harmful commands, logs user
# activity for analytics, and intelligently injects project and git context based on
# prompt keywords. Provides safety guards while enhancing AI responses with relevant context.
#
# Demonstrates: UserPromptSubmit hook usage, multi-pattern safety validation, intelligent
# context detection from prompts, structured JSON logging, and dynamic suggestion generation.

input=$(cat)

user_prompt=$(echo "$input" | jq -r '.userPromptSubmit.prompt')
task_id=$(echo "$input" | jq -r '.taskId')
user_id=$(echo "$input" | jq -r '.userId')

# Log user activity for analytics
activity_log="$HOME/.cline_user_activity/$(date +%Y-%m-%d).log"
mkdir -p "$(dirname "$activity_log")"

activity_entry=$(jq -n \
  --arg timestamp "$(date -Iseconds)" \
  --arg task_id "$task_id" \
  --arg user_id "$user_id" \
  --arg prompt_length "${#user_prompt}" \
  '{
    timestamp: $timestamp,
    task_id: $task_id,
    user_id: $user_id,
    prompt_length: ($prompt_length | tonumber),
    workspace: env.PWD
  }')

echo "$activity_entry" >> "$activity_log"

context_modifications=""
cancel_request=false

# Safety validation
harmful_patterns=("rm -rf" "delete.*all" "format.*drive" "sudo.*passwd")
for pattern in "${harmful_patterns[@]}"; do
  if echo "$user_prompt" | grep -qi "$pattern"; then
    cancel_request=true
    error_message="🚨 SAFETY ALERT: Potentially harmful command detected. Please review your request."
    break
  fi
done

# Intelligent context enhancement
if [[ "$cancel_request" == false ]]; then
  # Detect project context
  if echo "$user_prompt" | grep -qi "file\|directory\|folder"; then
    if [[ -f "package.json" ]]; then
      project_name=$(jq -r '.name // "unknown"' package.json 2>/dev/null)
      context_modifications+="PROJECT_CONTEXT: Working in Node.js project '$project_name'. "
    elif [[ -f "requirements.txt" ]]; then
      context_modifications+="PROJECT_CONTEXT: Working in Python project. "
    fi
  fi
  
  # Git context enhancement
  if echo "$user_prompt" | grep -qi "git\|commit\|branch" && git rev-parse --git-dir > /dev/null 2>&1; then
    current_branch=$(git branch --show-current 2>/dev/null)
    uncommitted=$(git status --porcelain | wc -l)
    context_modifications+="GIT_CONTEXT: On branch '$current_branch' with $uncommitted uncommitted changes. "
  fi
  
  # Tool suggestions
  if echo "$user_prompt" | grep -qi "search.*code\|find.*function"; then
    context_modifications+="SUGGESTION: Consider using search_files tool for code exploration. "
  fi
fi

# Return response
if [[ "$cancel_request" == true ]]; then
  jq -n --arg msg "$error_message" '{"cancel": true, "errorMessage": $msg}'
else
  if [[ -n "$context_modifications" ]]; then
    jq -n --arg ctx "$context_modifications" '{"cancel": false, "contextModification": $ctx}'
  else
    echo '{"cancel": false}'
  fi
fi
Key Concepts:
  • User interaction analysis and logging
  • Multi-pattern safety validation
  • Intelligent context detection
  • Dynamic suggestion generation

9. Multi-Service Integration Hub

Hook: PostToolUse
#!/usr/bin/env bash
# Multi-Service Integration Hub Hook
#
# Overview: Detects file modifications by type (dependencies, CI/CD, frontend, backend, tests)
# and sends asynchronous webhook notifications to multiple external services like Slack and
# CI/CD systems. Enables seamless integration of Cline operations into enterprise workflows.
#
# Demonstrates: Advanced pattern matching with associative arrays, multi-service webhook
# orchestration, asynchronous background processing, and enterprise notification patterns.

input=$(cat)

tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
success=$(echo "$input" | jq -r '.postToolUse.success')
file_path=$(echo "$input" | jq -r '.postToolUse.parameters.path // empty')

# Only process successful file operations
if [[ "$success" != "true" ]] || [[ "$tool_name" != "write_to_file" && "$tool_name" != "replace_in_file" ]]; then
  echo '{"cancel": false}'
  exit 0
fi

# Define workflow triggers
declare -A triggers=(
  ["package\\.json|yarn\\.lock"]="dependencies"
  ["\\.github/workflows/"]="ci_cd"
  ["src/.*component"]="frontend"
  ["api/.*\\.(ts|js)"]="backend"
  [".*\\.(test|spec)\\."]="testing"
)

# Determine triggered workflows
triggered_workflows=""
for pattern in "${!triggers[@]}"; do
  if [[ "$file_path" =~ $pattern ]]; then
    workflow_type="${triggers[$pattern]}"
    triggered_workflows+="$workflow_type "
  fi
done

context="WORKFLOW: File modified: $file_path"

if [[ -n "$triggered_workflows" ]]; then
  # Slack notification (async)
  slack_webhook="${SLACK_WEBHOOK_URL:-}"
  if [[ -n "$slack_webhook" ]]; then
    slack_payload=$(jq -n \
      --arg file "$file_path" \
      --arg workflows "$triggered_workflows" \
      --arg workspace "$(basename "$PWD")" \
      '{
        text: ("🔧 Cline modified `" + $file + "` in " + $workspace),
        color: "good",
        fields: [{
          title: "Triggered Workflows",
          value: $workflows,
          short: true
        }]
      }')
    
    (curl -X POST -H "Content-Type: application/json" -d "$slack_payload" "$slack_webhook" --max-time 5 --silent > /dev/null 2>&1) &
  fi

  # CI/CD webhook (async)  
  ci_webhook="${CI_WEBHOOK_URL:-}"
  if [[ -n "$ci_webhook" ]]; then
    ci_payload=$(jq -n \
      --arg file "$file_path" \
      --arg workflows "$triggered_workflows" \
      '{
        event: "file_modified",
        file_path: $file,
        workflows: ($workflows | split(" "))
      }')
    
    (curl -X POST -H "Content-Type: application/json" -d "$ci_payload" "$ci_webhook" --max-time 5 --silent > /dev/null 2>&1) &
  fi

  context+=" Triggered workflows: $triggered_workflows. Notifications sent to configured services."
fi

jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
Key Concepts:
  • Multi-service integration patterns
  • Asynchronous webhook orchestration
  • Complex workflow detection
  • Enterprise notification systems

Usage Tips

Running Multiple Hooks

You can use multiple hooks together by creating separate files for each hook type:
# Create hooks directory
mkdir -p .clinerules/hooks

# Create multiple hooks
touch .clinerules/hooks/PreToolUse
touch .clinerules/hooks/PostToolUse
touch .clinerules/hooks/TaskStart

# Make them executable
chmod +x .clinerules/hooks/*

Environment Configuration

Set up environment variables for external integrations:
# Add to your .bashrc or .zshrc
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."
export JIRA_URL="https://yourcompany.atlassian.net"
export JIRA_USER="[email protected]"
export JIRA_TOKEN="your-api-token"
export CI_WEBHOOK_URL="https://your-ci-system.com/hooks/cline"

Testing Your Hooks

Test hooks manually by simulating their input:
# Test a PreToolUse hook
echo '{
  "clineVersion": "1.0.0",
  "hookName": "PreToolUse",
  "timestamp": "2024-01-01T12:00:00Z",
  "taskId": "test",
  "workspaceRoots": ["/path/to/workspace"],
  "userId": "test-user",
  "preToolUse": {
    "toolName": "write_to_file",
    "parameters": {
      "path": "test.js",
      "content": "console.log(\"test\");"
    }
  }
}' | .clinerules/hooks/PreToolUse
These examples provide a solid foundation for implementing hooks in your development workflow. Customize them based on your specific needs, tools, and integrations.