Skip to content

Claude Hooks for Enhanced Context

Claude Code supports a powerful hooks system that allows you to inject custom context and behavior at different stages of the conversation lifecycle. This guide provides ideas for leveraging hooks to improve your AI coding workflow.


What Are Claude Hooks?

Hooks are scripts that run at specific events:

  • SessionStart - When a new session begins
  • UserPromptSubmit - Before sending user input to Claude
  • PostCompact - After compacting conversation history
  • Clear - After clearing the conversation

Hook Basics

Hooks are configured in ~/.claude/settings.json:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/your/hook-script.py",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

Hook Input/Output:

  • Hooks receive JSON via stdin with context data
  • Hooks can output text via stdout to inject into the conversation
  • Exit code 0 = success

Available Environment Variables:

  • CLAUDE_PROJECT_DIR - Current workspace path
  • CLAUDE_SESSION_ID - Current session identifier
  • Custom variables you set in settings.json

Practical Hook Ideas

1. Context Injection After /clear or /compact

When you clear or compact your conversation, you lose context. A hook can automatically re-inject important information:

Use case: After /clear, inject project structure, coding guidelines, or task context

Pattern:

#!/usr/bin/env python3
import json
import sys

hook_data = json.load(sys.stdin)
source = hook_data.get('source', '')

if source in ['clear', 'compact']:
    # Inject your context here
    print("Project: MyApp | Stack: Python/FastAPI | Current task: Refactoring auth")
    sys.exit(0)

sys.exit(0)

2. JSON Cleaning Utility

LLM-generated JSON often has formatting issues. A cleaner hook can fix common problems:

Use case: Clean malformed JSON from tool outputs or AI responses

Features to implement:

  • Remove comments (//, /* */)
  • Fix trailing commas
  • Replace smart quotes with regular quotes
  • Remove zero-width characters
  • Unescape quotes properly
  • Fix unescaped quotes in strings
  • Normalize line breaks

Example structure:

#!/usr/bin/env python3
import json
import re
import sys

def clean_json(data):
    # Remove comments
    data = re.sub(r'//.*?$|/\*.*?\*/|#.*?$', '', data, flags=re.MULTILINE)

    # Fix trailing commas
    data = re.sub(r',(\s*[\]}])', r'\1', data)

    # Replace smart quotes
    data = data.replace('"', '"').replace('"', '"')
    data = data.replace(''', "'").replace(''', "'")

    # Remove zero-width spaces
    for char in ['\u200b', '\u200c', '\u200d', '\u2060', '\ufeff']:
        data = data.replace(char, '')

    return data

# Use this in your hook logic

3. Dynamic Context Based on Project

Different projects need different context. A smart hook can detect the project and inject relevant info:

Use case: Auto-detect project type and inject appropriate guidelines

Pattern:

import os
from pathlib import Path

project_dir = os.environ.get('CLAUDE_PROJECT_DIR', '')

if Path(project_dir, 'package.json').exists():
    print("Node.js project detected. Using npm/pnpm conventions.")
elif Path(project_dir, 'requirements.txt').exists():
    print("Python project detected. Using black/pytest conventions.")
elif Path(project_dir, 'Cargo.toml').exists():
    print("Rust project detected. Using cargo conventions.")

4. Task Continuation After Context Loss

When context is cleared, ongoing tasks can be lost. A hook can preserve task state:

Use case: Remember what you were working on before /clear

Approach:

  1. On PreCompact/PreClear - Save current task/context to a file
  2. On SessionStart/PostClear - Reload and inject saved context
  3. Include: File references, decisions made, next steps

Conceptual flow:

User conversation → PreCompact hook → Save summary to ~/.claude/last_task.txt
User runs /clear → PostClear hook → Read ~/.claude/last_task.txt → Inject

5. Fun Engagement Hook

Keep conversations engaging with personality:

Use case: Start fresh conversations with a programming joke

Pattern:

if source == "clear":
    print("After clearing context, greet the user with a programming joke.")

This simple hook keeps the interaction light and fun after clearing context.


Hook Best Practices

Keep Hooks Fast

Hooks block the conversation flow. Keep execution under 5 seconds.

Recommendations:

  1. Cache when possible - Don't regenerate content on every hook call
  2. Use environment variables - Access CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID
  3. Log for debugging - Write to ~/.claude/hooks_debug.log for troubleshooting
  4. Handle errors gracefully - Always exit 0 even if hook logic fails
  5. Test incrementally - Start with simple hooks, add complexity gradually
  6. Avoid blocking operations - No network calls, heavy file I/O, or long computations
  7. Use proper shebang - #!/usr/bin/env python3 for portability

Complete Example: Minimal Context Injection

Here's a complete working example:

~/.claude/hooks/inject_context.py:

#!/usr/bin/env python3
import json
import sys
import os
from pathlib import Path

def main():
    try:
        hook_data = json.load(sys.stdin)
        source = hook_data.get('source', '')

        # Only inject after clear/compact
        if source not in ['clear', 'compact']:
            sys.exit(0)

        # Get project info
        project_dir = os.environ.get('CLAUDE_PROJECT_DIR', '')
        project_name = Path(project_dir).name if project_dir else 'Unknown'

        # Check if codebase is indexed
        codebase_state = Path(project_dir, '.codebase', 'state.json')
        indexed_status = "✅ Indexed" if codebase_state.exists() else "❌ Not indexed"

        # Inject context
        context = f"""
Project: {project_name}
Location: {project_dir}
Codebase: {indexed_status}
Remember to use semantic search for code references.
        """.strip()

        print(context)
        sys.exit(0)

    except Exception as e:
        # Silent fail - don't break the conversation
        sys.exit(0)

if __name__ == '__main__':
    main()

Make it executable:

chmod +x ~/.claude/hooks/inject_context.py

Configure in ~/.claude/settings.json:

{
  "hooks": {
    "PostCompact": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/inject_context.py",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

Ideas for Advanced Hooks

Context Supervisor

A sophisticated system that maintains conversation continuity:

Concept: - Track conversation topics and decisions - Generate summaries before compact - Re-inject critical decisions after clear - Maintain task continuity across sessions

Benefits: - Never lose important context - Smooth transitions after /clear - Automatic project context awareness - Task continuity across multiple days

Implementation hints: - Use PreCompact to analyze and save conversation state - Store summaries in ~/.claude/supervisors/<project-name>/ - Use PostClear to reload relevant context - Consider using an LLM to generate summaries (via API)

Smart File References

Automatically track and suggest relevant files:

Features: - Track which files were discussed in conversation - Maintain a "working set" of relevant files - Auto-suggest related files based on current topic - Persist file references across /clear

Use case:

Working on auth.py → Hook tracks it → After /clear → Hook reminds Claude about auth.py context

Custom Prompts by Project

Load project-specific guidelines automatically:

Structure:

~/.claude/project-prompts/
├── my-api/
│   ├── coding-style.txt
│   ├── architecture.txt
│   └── conventions.txt
└── frontend-app/
    ├── component-rules.txt
    └── state-management.txt

Hook logic:

project_name = Path(project_dir).name
prompts_dir = Path.home() / '.claude' / 'project-prompts' / project_name

if prompts_dir.exists():
    for prompt_file in prompts_dir.glob('*.txt'):
        with open(prompt_file) as f:
            print(f.read())

Integration with Codebase Index

Make hooks aware of your indexing status:

Capabilities: - Read .codebase/state.json for indexing status - Warn if index is stale (last update > 1 hour ago) - Inject collection name for semantic search tools - Remind Claude to use semantic search instead of file browsing

Example:

state_file = Path(project_dir, '.codebase', 'state.json')
if state_file.exists():
    state = json.loads(state_file.read_text())
    collection = state.get('qdrantCollection', 'unknown')
    total_vectors = state.get('qdrantStats', {}).get('totalVectors', 0)

    print(f"Codebase indexed: {total_vectors} vectors in collection {collection}")
    print("Use semantic_search tool for code queries.")

Debugging Hooks

If your hooks aren't working:

1. Check Claude Code Logs

# macOS
tail -f ~/Library/Logs/Claude/mcp*.log

# Linux
tail -f ~/.local/state/claude/logs/mcp*.log

# Custom hook log
tail -f ~/.claude/hooks_debug.log

2. Test Hook Manually

# Simulate hook input
echo '{"source": "clear", "session_id": "test"}' | ~/.claude/hooks/your-hook.py

# With project dir
CLAUDE_PROJECT_DIR=/path/to/project echo '{"source": "clear"}' | ~/.claude/hooks/your-hook.py

3. Verify Permissions

chmod +x ~/.claude/hooks/*.py

# Check if script is executable
ls -la ~/.claude/hooks/

4. Check Timeout

If your hook takes longer than the configured timeout, increase it:

{
  "type": "command",
  "command": "~/.claude/hooks/slow-hook.py",
  "timeout": 10  // Increase from default 5
}

5. Add Debug Logging

Add logging to your hooks for troubleshooting:

import logging
from pathlib import Path

log_file = Path.home() / '.claude' / 'hooks_debug.log'
logging.basicConfig(
    filename=str(log_file),
    level=logging.DEBUG,
    format='[%(asctime)s] %(levelname)s: %(message)s'
)

logging.debug(f"Hook called with source: {source}")
logging.debug(f"Project dir: {project_dir}")

6. Common Issues

Hook not running: - Check settings.json syntax (valid JSON) - Verify path to hook script is absolute - Ensure hook script is executable

Hook output not appearing: - Make sure you're printing to stdout (not stderr) - Check that exit code is 0 - Verify hook isn't timing out

Environment variables missing: - CLAUDE_PROJECT_DIR only available in SessionStart and some other hooks - Check Claude Code documentation for available variables per hook type


Hook Security Considerations

Security Best Practices

Hooks run with your user permissions. Be careful with external scripts.

Recommendations:

  1. Review hook scripts - Understand what they do before running
  2. Use absolute paths - Avoid relative paths that could be hijacked
  3. Validate input - Always validate JSON input from stdin
  4. Don't expose secrets - Never print API keys or sensitive data
  5. Sandbox if needed - Consider running untrusted hooks in containers

Ecosystem Integration

Hooks work seamlessly with the entire Codebase Index CLI ecosystem:

With Codebase Index CLI

  • Read .codebase/state.json for indexing status
  • Check if collection exists before injecting context
  • Remind Claude about available semantic search

With Semantic Search MCP

  • Inject collection names for MCP tools
  • Provide context about indexed commits
  • Guide Claude to use appropriate search strategies

With Claude Code Chat UI

  • Hooks complement the UI's status monitoring
  • Both provide real-time indexer awareness
  • Combine for complete workflow automation

Further Reading


Contributing Hook Examples

If you create useful hooks, consider sharing them with the community:

  1. Document what problem they solve
  2. Provide clear installation instructions
  3. Include example configuration
  4. Add debugging tips

This helps others learn and improves the ecosystem for everyone.