405 lines
8.3 KiB
Markdown
405 lines
8.3 KiB
Markdown
# 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`);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
``` |