Skip to main content

Claude Code Hooks System - Complete Documentation

🎯 Overview

The Claude Code hooks system is a powerful extensibility framework that allows developers to integrate custom functionality and automation directly into Claude Code workflows. This system enables real-time context injection, workflow enforcement, and seamless integration with external development tools.

Key Features:

  • Real-time context injection into user prompts
  • Tool execution validation and prevention
  • Custom slash command integration
  • External tool integration (TDDAI, Git, CI/CD)
  • Project-aware development assistance

🏗️ Architecture

Hook Types

Claude Code supports two primary hook types:

1. UserPromptSubmit Hook

  • Trigger: Fires when a user submits a prompt to Claude
  • Purpose: Inject context, project status, or additional information
  • Use Cases: TDD status, project metrics, Git state, environment info

2. PreToolUse Hook

  • Trigger: Fires before Claude executes any tool (Edit, Write, Bash, etc.)
  • Purpose: Validate, guard, or modify tool execution
  • Use Cases: Workflow enforcement, security checks, compliance validation

Configuration Structure

Hooks are configured through .claude/settings.json:

{
"hooks": {
"UserPromptSubmit": {
"command": "/absolute/path/to/.claude/hooks/context-injector.js"
},
"PreToolUse": {
"command": "/absolute/path/to/.claude/hooks/compliance-guardian.js"
}
}
}

Critical Requirements:

  • Absolute paths only - Relative paths will fail
  • Executable permissions - Scripts must be executable (chmod +x)
  • JSON input/output - Hooks communicate via JSON
  • Timeout handling - Hooks should complete within 5-10 seconds

🔧 Hook Implementation

Input/Output Format

Input Format

// UserPromptSubmit
{
"event": "UserPromptSubmit",
"prompt": "user's message",
"timestamp": "2024-01-01T12:00:00Z",
"context": {...}
}

// PreToolUse
{
"event": "PreToolUse",
"data": {
"toolName": "Edit",
"args": {
"file_path": "/path/to/file.js",
"old_string": "...",
"new_string": "..."
}
},
"timestamp": "2024-01-01T12:00:00Z"
}

Output Format

// Continue execution (default)
{
"continue": true,
"context": "Additional context to add to prompt"
}

// Prevent execution with reason
{
"continue": false,
"reason": "Explanation of why operation was blocked"
}

// Modify tool arguments (PreToolUse only)
{
"continue": true,
"modifiedArgs": {
"file_path": "/modified/path.js"
}
}

Example Hook Implementation

Context Injection Hook

#!/usr/bin/env node
// .claude/hooks/tdd-context-injector.js

const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

function main() {
try {
// Parse input
const input = JSON.parse(process.argv[2] || '{}');

if (input.event !== 'UserPromptSubmit') {
process.exit(0);
}

// Get project context
const context = gatherProjectContext();

// Output result
console.log(JSON.stringify({
continue: true,
context: context
}));

} catch (error) {
// Fail gracefully
process.exit(0);
}
}

function gatherProjectContext() {
const contexts = [];

// TDD Status
const tddStatus = getTDDStatus();
if (tddStatus) {
contexts.push(`[TDD Status] ${tddStatus}`);
}

// Project Info
const projectInfo = getProjectInfo();
if (projectInfo) {
contexts.push(`[Project Context] ${projectInfo}`);
}

// Git Status
const gitStatus = getGitStatus();
if (gitStatus) {
contexts.push(`[Git] ${gitStatus}`);
}

return contexts.join(' | ');
}

function getTDDStatus() {
try {
// Check for TDD state file
if (fs.existsSync('.tdd-state.json')) {
const state = JSON.parse(fs.readFileSync('.tdd-state.json', 'utf8'));
return `Phase: ${state.phase}, Coverage: ${state.coverage}%`;
}

// Run TDDAI analysis
const result = execSync('npx @bluefly/tddai analyze --json', {
timeout: 5000,
encoding: 'utf8'
});

const analysis = JSON.parse(result);
return `Phase: ${analysis.phase}, Coverage: ${analysis.coverage}%`;
} catch (error) {
return null;
}
}

function getProjectInfo() {
try {
// Count tracked files
const files = execSync('git ls-files', {
timeout: 2000,
encoding: 'utf8'
}).split('\n').filter(f => f.trim());

// Get project type
const projectType = detectProjectType();

return `${files.length} files tracked, Type: ${projectType}`;
} catch (error) {
return null;
}
}

function detectProjectType() {
if (fs.existsSync('composer.json')) return 'PHP/Drupal';
if (fs.existsSync('package.json')) return 'Node.js';
if (fs.existsSync('requirements.txt')) return 'Python';
return 'Unknown';
}

function getGitStatus() {
try {
const branch = execSync('git branch --show-current', {
timeout: 1000,
encoding: 'utf8'
}).trim();

const status = execSync('git status --porcelain', {
timeout: 1000,
encoding: 'utf8'
});

const changes = status.split('\n').filter(line => line.trim()).length;

return `Branch: ${branch}, Changes: ${changes}`;
} catch (error) {
return null;
}
}

main();

Compliance Guardian Hook

#!/usr/bin/env node
// .claude/hooks/tdd-compliance-guardian.js

const fs = require('fs');
const path = require('path');

function main() {
try {
const input = JSON.parse(process.argv[2] || '{}');

if (input.event !== 'PreToolUse') {
process.exit(0);
}

const validation = validateToolUse(input.data);
console.log(JSON.stringify(validation));

} catch (error) {
// Fail gracefully - allow operation
console.log(JSON.stringify({ continue: true }));
process.exit(0);
}
}

function validateToolUse(toolData) {
const { toolName, args } = toolData;

// Only validate file operations
if (!['Edit', 'Write', 'MultiEdit'].includes(toolName)) {
return { continue: true };
}

const filePath = args.file_path;
if (!filePath) {
return { continue: true };
}

// Check TDD phase restrictions
const tddValidation = validateTDDPhase(filePath, toolName);
if (!tddValidation.continue) {
return tddValidation;
}

// Check security restrictions
const securityValidation = validateSecurity(filePath, toolName);
if (!securityValidation.continue) {
return securityValidation;
}

return { continue: true };
}

function validateTDDPhase(filePath, toolName) {
try {
// Read TDD state
const tddState = readTDDState();
if (!tddState) {
return { continue: true };
}

const isTestFile = isTestFilePath(filePath);
const isImplementationFile = !isTestFile && isSourceFile(filePath);

switch (tddState.phase) {
case 'RED':
if (isImplementationFile) {
return {
continue: false,
reason: `TDD RED Phase Violation: Cannot modify implementation file '${path.basename(filePath)}' during RED phase. Write failing tests first.`
};
}
break;

case 'GREEN':
if (isTestFile && toolName === 'Edit') {
return {
continue: true,
context: `⚠️ TDD GREEN Phase: You're modifying tests during implementation. Ensure this is intentional.`
};
}
break;

case 'REFACTOR':
// Allow all operations but remind about keeping tests green
if (isImplementationFile) {
return {
continue: true,
context: `♻️ TDD REFACTOR Phase: Ensure tests remain green after changes.`
};
}
break;
}

return { continue: true };
} catch (error) {
return { continue: true };
}
}

function validateSecurity(filePath, toolName) {
// Prevent editing sensitive files
const sensitivePatterns = [
/\.env$/,
/\.key$/,
/\.pem$/,
/id_rsa$/,
/\.secret$/
];

if (sensitivePatterns.some(pattern => pattern.test(filePath))) {
return {
continue: false,
reason: `Security: Cannot modify sensitive file '${path.basename(filePath)}'`
};
}

return { continue: true };
}

function readTDDState() {
try {
if (fs.existsSync('.tdd-state.json')) {
return JSON.parse(fs.readFileSync('.tdd-state.json', 'utf8'));
}
} catch (error) {
// Ignore errors
}
return null;
}

function isTestFilePath(filePath) {
const testPatterns = [
/\.test\.(js|ts|php)$/,
/\.spec\.(js|ts|php)$/,
/__tests__\//,
/\/tests?\//,
/Test\.php$/
];

return testPatterns.some(pattern => pattern.test(filePath));
}

function isSourceFile(filePath) {
const sourcePatterns = [
/\.(js|ts|php|py)$/,
/\.(jsx|tsx)$/
];

const excludePatterns = [
/node_modules\//,
/vendor\//,
/\.git\//,
/dist\//,
/build\//
];

return sourcePatterns.some(pattern => pattern.test(filePath)) &&
!excludePatterns.some(pattern => pattern.test(filePath));
}

main();

🎮 Slash Commands Integration

Command Structure

Slash commands are defined in .claude/commands/tdd/ directory:

.claude/commands/tdd/
├── start.md
├── status.md
├── validate.md
├── red.md
├── green.md
└── refactor.md

Example Slash Command

---
description: Initialize TDD session and analyze project
allowed-tools: Bash(npx:*)
---

# TDD Start

Initialize TDD session with comprehensive project analysis.

!npx @bluefly/tddai analyze --detailed

## What This Does

1. Analyzes project structure and test coverage
2. Detects testing framework and configuration
3. Sets up TDD state tracking
4. Provides quality score and recommendations

## Next Steps

After initialization, use:
- `/tdd-red feature-name` to start writing tests
- `/tdd-status` to check current metrics
- `/tdd-validate` for comprehensive validation

Command Registration

// .claude/settings.json
{
"hooks": {...},
"commands": {
"tdd": {
"description": "TDD workflow management",
"commands": {
"start": "./commands/tdd/start.md",
"status": "./commands/tdd/status.md",
"red": "./commands/tdd/red.md",
"green": "./commands/tdd/green.md",
"refactor": "./commands/tdd/refactor.md",
"validate": "./commands/tdd/validate.md"
}
}
}
}

🚀 Setup and Installation

# Install TDDAI globally
npm install -g @bluefly/tddai

# Automatic Claude Code integration
npx @bluefly/tddai claude-setup

This creates the complete hook infrastructure:

.claude/
├── settings.json # Hook configuration
├── hooks/
│ ├── tdd-context-injector.js # Context injection
│ └── tdd-compliance-guardian.js # Workflow enforcement
└── commands/tdd/
├── start.md # /tdd-start command
├── status.md # /tdd-status command
├── validate.md # /tdd-validate command
├── red.md # /tdd-red command
├── green.md # /tdd-green command
└── refactor.md # /tdd-refactor command

Manual Setup

# 1. Create directory structure
mkdir -p .claude/hooks .claude/commands/tdd

# 2. Create hook scripts (see examples above)
# 3. Make hooks executable
chmod +x .claude/hooks/*.js

# 4. Create settings.json with absolute paths
# 5. Create command markdown files
# 6. Test hooks manually

Verification

# Test context injection hook
./.claude/hooks/tdd-context-injector.js '{"event":"UserPromptSubmit","prompt":"test"}'

# Test compliance guardian hook
./.claude/hooks/tdd-compliance-guardian.js '{"event":"PreToolUse","data":{"toolName":"Edit","args":{"file_path":"src/app.js"}}}'

# Verify slash commands work
# In Claude Code: /tdd-start

🔧 Advanced Configuration

Multi-Hook Support

{
"hooks": {
"UserPromptSubmit": [
{
"command": "/path/to/tdd-context-injector.js",
"priority": 1
},
{
"command": "/path/to/git-context-injector.js",
"priority": 2
}
]
}
}

Conditional Hook Execution

// Check project type before running
function shouldRunHook() {
if (!fs.existsSync('package.json') && !fs.existsSync('composer.json')) {
return false; // Not a development project
}

// Check if user opted out
if (process.env.TDDAI_HOOKS_DISABLED === 'true') {
return false;
}

return true;
}

Performance Optimization

// Cache expensive operations
const cache = new Map();

function getCachedProjectInfo() {
const cacheKey = `project_info_${process.cwd()}`;

if (cache.has(cacheKey)) {
const cached = cache.get(cacheKey);
if (Date.now() - cached.timestamp < 30000) { // 30 second cache
return cached.data;
}
}

const data = computeProjectInfo();
cache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}

🛠️ Development Best Practices

Error Handling

function safeExecute(command, timeout = 5000) {
try {
return execSync(command, {
timeout,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'] // Suppress stderr
});
} catch (error) {
return null; // Fail gracefully
}
}

Security Considerations

// Validate file paths
function isValidPath(filePath) {
// Prevent directory traversal
if (filePath.includes('..')) return false;

// Ensure path is within project
const resolved = path.resolve(filePath);
const projectRoot = process.cwd();
return resolved.startsWith(projectRoot);
}

// Sanitize shell commands
function sanitizeCommand(cmd) {
// Remove dangerous characters
return cmd.replace(/[;&|`$(){}[\]]/g, '');
}

Logging and Debugging

const DEBUG = process.env.TDDAI_DEBUG === 'true';

function debugLog(message, data = null) {
if (DEBUG) {
const timestamp = new Date().toISOString();
console.error(`[${timestamp}] TDDAI Hook: ${message}`);
if (data) {
console.error(JSON.stringify(data, null, 2));
}
}
}

🔍 Troubleshooting

Common Issues

Hook Not Executing

# Check settings.json syntax
cat .claude/settings.json | jq .

# Verify absolute paths
ls -la /absolute/path/to/hook.js

# Check permissions
chmod +x .claude/hooks/*.js

# Test manually
./.claude/hooks/hook.js '{"event":"UserPromptSubmit","prompt":"test"}'

Hook Timing Out

# Check hook performance
time ./.claude/hooks/hook.js '{"event":"UserPromptSubmit","prompt":"test"}'

# Add timeout handling
timeout 10s ./.claude/hooks/hook.js '{"event":"UserPromptSubmit","prompt":"test"}'

JSON Parsing Errors

// Robust JSON parsing
function parseInput() {
try {
const input = process.argv[2];
if (!input) return { event: 'unknown' };
return JSON.parse(input);
} catch (error) {
debugLog('JSON parse error:', error.message);
return { event: 'unknown' };
}
}

Debug Mode

# Enable debug logging
export TDDAI_DEBUG=true

# Run Claude Code with verbose logging
export CLAUDE_DEBUG=true

# Check hook execution
tail -f ~/.claude/hooks.log

🎯 Use Cases and Examples

Project Context Injection

  • Git status: Current branch, uncommitted changes
  • Test coverage: Real-time coverage percentages
  • Dependencies: Outdated or vulnerable packages
  • Environment: Development, staging, production context

Workflow Enforcement

  • TDD phases: Prevent implementation during RED phase
  • Code reviews: Block commits without proper reviews
  • Security: Prevent editing sensitive configuration files
  • Standards: Enforce coding standards and conventions

Team Collaboration

  • Shared context: Team-wide project information
  • Notifications: Alert team about important changes
  • Metrics: Shared code quality and coverage metrics
  • Compliance: Ensure team follows established workflows

CI/CD Integration

  • Build status: Show current build state in prompts
  • Deploy gates: Prevent deployment without proper validation
  • Quality gates: Enforce quality thresholds before merge
  • Security scans: Show security scan results

🚀 Future Enhancements

Planned Features

  • Multi-IDE support: VS Code, Cursor, JetBrains
  • Advanced context: AI-powered context analysis
  • Team dashboards: Real-time team collaboration metrics
  • Custom hook marketplace: Shareable hook library

Integration Roadmap

  • Cursor Agent: Data sovereignty and private AI
  • Vector databases: Pattern learning and suggestions
  • Enterprise features: SSO, audit logs, compliance reporting
  • Mobile support: Claude Code mobile app integration

📚 Resources

Documentation

Examples

Support


Claude Code Hooks - Extensible AI-Powered Development
© 2024 LLM Platform Team. Licensed under MIT.