8.3 KiB
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:
{
"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:
{
"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:
{
"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:
{
"allow": true,
"message": "Context usage: 67%"
}
PreToolUse
Trigger: Before Claude executes any tool
Purpose: Validate operations, block dangerous commands
PreToolUse[Bash]
Input:
{
"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:
{
"allow": false,
"message": "⛔ Command blocked: Dangerous pattern detected"
}
Warning response:
{
"allow": true,
"message": "⚠️ Command may fail (confidence: 85%)\n💡 Suggestion: Use 'python3' instead of 'python'"
}
PreToolUse[Edit]
Input:
{
"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:
{
"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:
{
"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:
{
"allow": true,
"message": "Logged Bash execution (learned failure pattern)"
}
Stop
Trigger: When Claude session ends
Purpose: Finalize session, create documentation
Input:
{}
Expected behavior:
- Always return
"allow": true
- Create LAST_SESSION.md
- Update ACTIVE_TODOS.md
- Save learned patterns
- Generate recovery information if needed
Example response:
{
"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:
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:
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:
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:
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:
# 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:
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`);
}
});
}
}