Workflow Routing (SYSTEM PROMPT)
When user requests creating a new hook: Examples: "create a hook for X", "add a hook", "make a SessionStart hook" -> READ: ${PAI_DIR}/skills/hook-authoring/workflows/create-hook.md -> EXECUTE: Follow hook creation workflow
When user needs to debug hooks: Examples: "hook not working", "debug hooks", "hook troubleshooting" -> READ: ${PAI_DIR}/skills/hook-authoring/workflows/debug-hooks.md -> EXECUTE: Run debugging checklist
Claude Code Hook Events
SessionStart
When: New Claude Code session begins Use Cases: Load context, initialize state, capture metadata
PreToolUse
When: Before a tool executes Use Cases: Security checks, permission enforcement, input validation
PostToolUse
When: After a tool executes Use Cases: Logging, metrics, audit trail
UserPromptSubmit
When: User submits a prompt Use Cases: Pre-processing, context injection, tab updates
Stop
When: Claude completes a response (not user) Use Cases: Extract completion info, update tab titles, capture work
SubagentStop
When: A subagent (Task) completes Use Cases: Agent tracking, result capture, coordination
ConfigChange
When: Settings or config files change Use Cases: Sync validation, drift detection
Hook Configuration
Location: ${PAI_DIR}/.claude/settings.json or ~/.claude/settings.json
{ "hooks": { "SessionStart": [ { "matcher": {}, "hooks": [ { "type": "command", "command": "${PAI_DIR}/hooks/my-hook.ts" } ] } ] } }
Hook Input/Output
Input (stdin JSON)
interface HookInput { session_id: string; transcript_path: string; // Event-specific fields... }
Output (stdout)
-
Continue: { "continue": true }
-
Block: { "continue": false, "reason": "..." }
-
Inject content: { "result": "<system-reminder>...</system-reminder>" }
-
Add context (CC 2.1.9+): { "decision": "continue", "additionalContext": "..." }
Session ID Tracking (CC 2.1.9+)
Hooks receive session_id in input JSON. For persistent storage:
// Use session_id from hook input
const sessionFile = ${PAI_DIR}/state/sessions/${input.session_id}.json;
// Or use ${CLAUDE_SESSION_ID} substitution in settings.json: { "hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "${PAI_DIR}/hooks/init-session.ts --session ${CLAUDE_SESSION_ID}" }] }] } }
PAI Active Hooks
Hook Event Purpose
session-start.ts
SessionStart Load CORE skill, initialize session
pre-tool-use-security.ts
PreToolUse Block dangerous Bash commands
post-tool-use.ts
PostToolUse JSONL tool usage logging
update-tab-titles.ts
UserPromptSubmit Set terminal tab titles
stop-hook.ts
Stop Checkpoint logging, tab update
config-change.ts
ConfigChange Settings sync validation
Hook Best Practices
-
Fail gracefully - Hooks should never block Claude
-
Timeout protection - Use timeouts for external calls
-
Async by default - Don't block the main process
-
TypeScript preferred - Use Bun for execution
-
Log errors - Don't fail silently
Quick Hook Template
#!/usr/bin/env bun // ${PAI_DIR}/hooks/my-hook.ts
import { readFileSync } from 'fs';
const input = JSON.parse(readFileSync(0, 'utf-8'));
// Your logic here
// Output (choose one): console.log(JSON.stringify({ continue: true })); // console.log(JSON.stringify({ result: "<system-reminder>...</system-reminder>" }));
Make executable: chmod +x my-hook.ts
Related
-
See post-tool-use.ts for JSONL logging pattern
-
See hook-test skill for hook health checking