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
Automatic Setup (Recommended)
# 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.