# Hook API Reference ## Hook Input/Output Protocol All hooks communicate with Claude Code via JSON over stdin/stdout. ### Input Format Hooks receive a JSON object via stdin containing: ```json { "tool": "string", // Tool being used (e.g., "Bash", "Read", "Edit") "parameters": {}, // Tool-specific parameters "success": boolean, // Tool execution result (PostToolUse only) "error": "string", // Error message if failed (PostToolUse only) "execution_time": number, // Execution time in seconds (PostToolUse only) "prompt": "string" // User prompt text (UserPromptSubmit only) } ``` ### Output Format Hooks must output a JSON response to stdout: ```json { "allow": boolean, // Required: true to allow, false to block "message": "string" // Optional: message to display to user } ``` ### Exit Codes - `0` - Allow operation (success) - `1` - Block operation (for PreToolUse hooks only) - Any other code - Treated as hook error, operation allowed --- ## Hook Types ### UserPromptSubmit **Trigger**: When user submits a prompt to Claude **Purpose**: Monitor session state, trigger backups **Input**: ```json { "prompt": "Create a new Python script that..." } ``` **Expected behavior**: - Always return `"allow": true` - Update context usage estimates - Trigger backups if thresholds exceeded - Update session tracking **Example response**: ```json { "allow": true, "message": "Context usage: 67%" } ``` --- ### PreToolUse **Trigger**: Before Claude executes any tool **Purpose**: Validate operations, block dangerous commands #### PreToolUse[Bash] **Input**: ```json { "tool": "Bash", "parameters": { "command": "rm -rf /tmp/myfiles", "description": "Clean up temporary files" } } ``` **Expected behavior**: - Return `"allow": false` and exit code 1 to block dangerous commands - Return `"allow": true` with warnings for suspicious commands - Check against learned failure patterns - Suggest alternatives when blocking **Block response**: ```json { "allow": false, "message": "⛔ Command blocked: Dangerous pattern detected" } ``` **Warning response**: ```json { "allow": true, "message": "⚠️ Command may fail (confidence: 85%)\n💡 Suggestion: Use 'python3' instead of 'python'" } ``` #### PreToolUse[Edit] **Input**: ```json { "tool": "Edit", "parameters": { "file_path": "/etc/passwd", "old_string": "user:x:1000", "new_string": "user:x:0" } } ``` **Expected behavior**: - Validate file paths for security - Check for system file modifications - Prevent path traversal attacks #### PreToolUse[Write] **Input**: ```json { "tool": "Write", "parameters": { "file_path": "../../sensitive_file.txt", "content": "malicious content" } } ``` **Expected behavior**: - Validate file paths - Check file extensions - Detect potential security issues --- ### PostToolUse **Trigger**: After Claude executes any tool **Purpose**: Learn from outcomes, log activity **Input**: ```json { "tool": "Bash", "parameters": { "command": "pip install requests", "description": "Install requests library" }, "success": false, "error": "bash: pip: command not found", "execution_time": 0.125 } ``` **Expected behavior**: - Always return `"allow": true` - Update learning patterns based on success/failure - Log execution for analysis - Update session state **Example response**: ```json { "allow": true, "message": "Logged Bash execution (learned failure pattern)" } ``` --- ### Stop **Trigger**: When Claude session ends **Purpose**: Finalize session, create documentation **Input**: ```json {} ``` **Expected behavior**: - Always return `"allow": true` - Create LAST_SESSION.md - Update ACTIVE_TODOS.md - Save learned patterns - Generate recovery information if needed **Example response**: ```json { "allow": true, "message": "Session finalized. Modified 5 files, used 23 tools." } ``` --- ## Error Handling ### Hook Errors If a hook script: - Exits with non-zero code (except 1 for PreToolUse) - Produces invalid JSON - Times out (> 5 seconds default) - Crashes or cannot be executed Then Claude Code will: - Log the error - Allow the operation to proceed - Display a warning message ### Recovery Behavior Hooks are designed to fail safely: - Operations are never blocked due to hook failures - Invalid responses are treated as "allow" - Missing hooks are ignored - Malformed JSON output allows operation --- ## Performance Requirements ### Execution Time - **UserPromptSubmit**: < 100ms recommended - **PreToolUse**: < 50ms recommended (blocking user) - **PostToolUse**: < 200ms recommended (can be async) - **Stop**: < 1s recommended (cleanup operations) ### Memory Usage - Hooks should use < 50MB memory - Avoid loading large datasets on each execution - Use caching for expensive operations ### I/O Operations - Minimize file I/O in PreToolUse hooks - Batch write operations where possible - Use async operations for non-blocking hooks --- ## Security Considerations ### Input Validation Always validate hook input: ```javascript function validateInput(inputData) { if (typeof inputData !== 'object' || inputData === null || Array.isArray(inputData)) { throw new Error('Input must be JSON object'); } const tool = inputData.tool || ''; if (typeof tool !== 'string') { throw new Error('Tool must be string'); } // Validate other fields... } ``` ### Output Sanitization Ensure hook output is safe: ```javascript function safeMessage(text) { // Remove potential injection characters return text.replace(/\x00/g, '').replace(/\r/g, '').replace(/\n/g, '\\n'); } const response = { allow: true, message: safeMessage(userInput) }; ``` ### File Path Validation For hooks that access files: ```javascript const path = require('path'); function validateFilePath(filePath) { // Convert to absolute path const absPath = path.resolve(filePath); // Check if within project boundaries const projectRoot = path.resolve('.'); if (!absPath.startsWith(projectRoot)) { throw new Error('Path outside project directory'); } // Check for system files const systemPaths = ['/etc', '/usr', '/var', '/sys', '/proc']; for (const sysPath of systemPaths) { if (absPath.startsWith(sysPath)) { throw new Error('System file access denied'); } } } ``` --- ## Testing Hooks ### Unit Testing Test hooks with sample inputs: ```javascript const { spawn } = require('child_process'); function testCommandValidator() { // Test dangerous command const inputData = { tool: 'Bash', parameters: { command: 'rm -rf /' } }; const process = spawn('node', ['hooks/command-validator.js'], { stdio: 'pipe' }); process.stdin.write(JSON.stringify(inputData)); process.stdin.end(); process.on('exit', (code) => { console.assert(code === 1, 'Should block'); // Should block }); process.stdout.on('data', (data) => { const response = JSON.parse(data.toString()); console.assert(response.allow === false); }); } ``` ### Integration Testing Test with Claude Code directly: ```bash # Test in development environment echo '{"tool": "Bash", "parameters": {"command": "ls"}}' | node hooks/command-validator.js # Test hook registration claude-hooks status ``` ### Performance Testing Measure hook execution time: ```javascript const { spawn } = require('child_process'); function benchmarkHook(hookScript, inputData, iterations = 100) { const times = []; let completed = 0; for (let i = 0; i < iterations; i++) { const start = Date.now(); const process = spawn('node', [hookScript], { stdio: 'pipe' }); process.stdin.write(JSON.stringify(inputData)); process.stdin.end(); process.on('exit', () => { times.push(Date.now() - start); completed++; if (completed === iterations) { const avgTime = times.reduce((a, b) => a + b, 0) / times.length; const maxTime = Math.max(...times); console.log(`Average: ${avgTime.toFixed(1)}ms, Max: ${maxTime.toFixed(1)}ms`); } }); } } ```