---
title: "Samples"
sidebarTitle: "Samples"
description: "Practical hook examples organized by complexity level - from beginner to advanced patterns"
---

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`

```bash
#!/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`

```bash
#!/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`

```bash
#!/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`

```bash
#!/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`

```bash
#!/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`

```bash
#!/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`

```bash
#!/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`

```bash
#!/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`

```bash
#!/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:

```bash
# 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:

```bash
# 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="your-email@company.com"
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:

```bash
# 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.
