Hook Creator Skill
Creates, validates, and registers hooks for the multi-agent orchestration framework.
ROUTER UPDATE REQUIRED (CRITICAL - DO NOT SKIP)
After creating ANY hook, you MUST update documentation:
- Add to .claude/hooks/README.md under appropriate category
- Register in config.yaml or settings.json if required
- Update learnings.md with hook summary
Verification:
grep "
WHY: Hooks not documented are invisible and unmaintainable.
Overview
This skill creates hooks for the Claude Code framework:
Pre-tool execution - Safety validation before commands run Post-tool execution - Logging, memory updates, telemetry Session lifecycle - Initialize context, cleanup on exit Memory management - Auto-extract learnings, format memory files Routing enforcement - Ensure Router-First protocol compliance Hook Types Type Location Purpose When Triggered Safety .claude/hooks/safety/ Validate commands, block dangerous ops Before Bash/Write/Edit Memory .claude/hooks/memory/ Auto-update learnings, extract insights After task completion Routing .claude/hooks/routing/ Enforce router-first protocol On UserPromptSubmit Session .claude/hooks/session/ Initialize/cleanup sessions Session start/end Claude Code Hook Types Hook Event When Triggered Use Case PreToolUse Before tool executes Validation, blocking, permission checks PostToolUse After tool completes Logging, cleanup, notifications UserPromptSubmit Before model sees message Routing, intent analysis, filtering Workflow Steps Reference Hook
Use .claude/hooks/routing/router-enforcer.cjs as the canonical reference hook.
Before finalizing any hook, compare against router-enforcer structure:
Has proper CommonJS exports (module.exports) Exports required functions for hook type (validate, main, etc.) Has comprehensive test file (.test.cjs) Has proper error handling (try/catch, graceful fallbacks) Returns correct response format for hook type Step 1: Gather Hook Requirements
Before creating a hook, gather:
Purpose: What should this hook do? Trigger: When should it run? (pre-tool, post-tool, session event) Target tools: Which tools does it apply to? (Bash, Write, Edit, Read) Behavior: Block operation or warn only? Exit codes: What indicates success/failure? // Example requirements gathering { purpose: "Validate git push commands to prevent force push", trigger: "pre-tool (Bash)", target_tools: ["Bash"], behavior: "block if force push detected", exit_codes: { 0: "allow", 1: "block" } }
Step 2: Determine Hook Type and Location If hook does... Type Location Validates commands Safety .claude/hooks/safety/ Modifies routing Routing .claude/hooks/routing/ Updates memory Memory .claude/hooks/memory/ Session init/cleanup Session .claude/hooks/session/
Naming Convention:
Examples:
validate-git-force-push.cjs enforce-tdd-workflow.cjs extract-workflow-learnings.cjs memory-reminder.cjs Step 3: Generate Hook Code (CJS Format)
All hooks use CommonJS format and follow this template:
'use strict';
/* * {Hook Name} * * Type: {pre|post}-{tool} | session-{start|end} | user-prompt * Purpose: {One line description} * Trigger: {When this hook runs} * * Exit codes: * - 0: Allow operation (with optional warning) * - 1: Block operation (when in blocking mode) * * Environment: * {HOOK_NAME}_MODE=block|warn|off (default: warn) /
const fs = require('fs'); const path = require('path');
// Find project root by looking for .claude directory function findProjectRoot() { let dir = __dirname; while (dir !== path.parse(dir).root) { if (fs.existsSync(path.join(dir, '.claude'))) { return dir; } dir = path.dirname(dir); } return process.cwd(); }
const PROJECT_ROOT = findProjectRoot(); const ENFORCEMENT_MODE = process.env.HOOK_NAME_MODE || 'warn';
/* * Parse hook input from Claude Code * Claude Code passes JSON via process.argv[2] for hooks * @returns {Object|null} Parsed hook input or null / function parseHookInput() { try { if (process.argv[2]) { return JSON.parse(process.argv[2]); } } catch (e) { // Fallback for testing or invalid input } return null; }
/* * Validate hook - called by Claude Code or programmatically * @param {Object} context - Hook context with tool info * @param {string} context.tool - Tool name (Bash, Write, Edit, Read) * @param {Object} context.parameters - Tool parameters * @returns {{ valid: boolean, error: string, warning?: string }} / function validate(context) { const { tool, parameters } = context;
// YOUR VALIDATION LOGIC HERE
// Return validation result return { valid: true, error: '' }; }
/* * Main execution for CLI hook usage / function main() { // Skip if enforcement is off if (ENFORCEMENT_MODE === 'off') { process.exit(0); }
const hookInput = parseHookInput(); if (!hookInput) { process.exit(0); }
// Get tool name and input const toolName = hookInput.tool_name || hookInput.tool; const toolInput = hookInput.tool_input || hookInput.input || {};
// Run validation const result = validate({ tool: toolName, parameters: toolInput });
if (!result.valid) {
if (ENFORCEMENT_MODE === 'block') {
console.error(BLOCKED: ${result.error});
process.exit(1);
} else {
console.warn(WARNING: ${result.error});
process.exit(0);
}
}
if (result.warning) {
console.warn(WARNING: ${result.warning});
}
process.exit(0); }
// Run main if executed directly if (require.main === module) { main(); }
// Export for programmatic use and testing module.exports = { validate, findProjectRoot, PROJECT_ROOT, };
Step 4: Create Test File
Every hook MUST have a corresponding test file:
'use strict';
const { validate } = require('./hook-name.cjs');
describe('Hook Name', () => { test('allows valid operations', () => { const result = validate({ tool: 'Bash', parameters: { command: 'git status' }, }); expect(result.valid).toBe(true); expect(result.error).toBe(''); });
test('blocks dangerous operations', () => { const result = validate({ tool: 'Bash', parameters: { command: 'git push --force' }, }); expect(result.valid).toBe(false); expect(result.error).toContain('force push'); });
test('handles missing parameters gracefully', () => { const result = validate({ tool: 'Bash', parameters: {}, }); expect(result.valid).toBe(true); }); });
Step 5: Register Hook (If Needed)
Some hooks require registration in config files:
For pre/post-tool hooks (settings.json):
{ "hooks": { "pre-tool": [".claude/hooks/safety/hook-name.cjs"], "post-tool": [".claude/hooks/memory/hook-name.cjs"] } }
For event hooks (config.yaml):
hooks: UserPromptSubmit: - path: .claude/hooks/routing/hook-name.cjs type: command SessionStart: - path: .claude/hooks/session/hook-name.cjs type: command
Step 6: Update Documentation (MANDATORY - BLOCKING)
After creating a hook, update .claude/hooks/README.md:
{Hook Name} (hook-name.cjs)
{Description of what the hook does}
When it runs: {Trigger condition} What it checks/does: {Detailed behavior}
Verify with:
grep "hook-name" .claude/hooks/README.md || echo "ERROR: Not documented!"
Step 7: System Impact Analysis (MANDATORY)
This analysis is MANDATORY. Hook creation is INCOMPLETE without it.
After creating a hook:
Settings Registration (BLOCKING)
Add to .claude/settings.json PreToolUse/PostToolUse/etc. Verify with: grep "hook-name" .claude/settings.json
Test Coverage (BLOCKING)
Create .test.cjs file with minimum 10 test cases
Run tests: node .claude/hooks/
Documentation
Update .claude/docs/ if hook adds new capability Add usage examples
Related Hooks
Check if hook affects other hooks in same trigger category Document interaction patterns
Full Checklist:
[HOOK-CREATOR] System Impact Analysis for:
-
HOOK FILE CREATED [ ] Created at .claude/hooks/
/ .cjs [ ] Follows CJS format with validate() export [ ] Has main() function for CLI execution [ ] Handles graceful degradation (warn by default) -
TEST FILE CREATED (minimum 10 test cases) [ ] Created at .claude/hooks/
/ .test.cjs [ ] Tests valid operations (3+ cases) [ ] Tests blocked operations (3+ cases) [ ] Tests edge cases (3+ cases) [ ] Tests error handling (1+ cases) -
DOCUMENTATION UPDATED [ ] Added to .claude/hooks/README.md [ ] Documented trigger conditions [ ] Documented exit codes
-
REGISTRATION (BLOCKING) [ ] Added to settings.json (pre/post-tool hooks) [ ] Added to config.yaml (event hooks) [ ] Verified: grep "
" .claude/settings.json -
MEMORY UPDATED [ ] Added to learnings.md with hook summary
BLOCKING: If ANY item above is missing, hook creation is INCOMPLETE.
CLI Reference
Create hook using CLI tool
node .claude/tools/hook-creator/create-hook.mjs \ --name "hook-name" \ --type "PreToolUse|PostToolUse|UserPromptSubmit" \ --purpose "Description of what the hook does" \ --category "safety|routing|memory|audit|security|validation|custom" \ --matcher "Edit|Write|Bash" # Optional: tool matcher regex
List all hooks
node .claude/tools/hook-creator/create-hook.mjs --list
Validate hook structure
node .claude/tools/hook-creator/create-hook.mjs --validate "
Assign to agents
node .claude/tools/hook-creator/create-hook.mjs --assign "name" --agents "agent1,agent2"
Unregister hook
node .claude/tools/hook-creator/create-hook.mjs --unregister "
Test with sample input
node .claude/hooks/
Hook Patterns Reference Pattern 1: Safety Validator (Pre-Tool)
For validating commands before execution:
'use strict';
/* * Validate Git Force Push * Prevents accidental force pushes to protected branches /
const PROTECTED_BRANCHES = ['main', 'master', 'production'];
function validate(context) { const { tool, parameters } = context;
if (tool !== 'Bash') { return { valid: true, error: '' }; }
const command = parameters?.command || '';
// Check for force push
if (command.includes('git push') && (command.includes('--force') || command.includes('-f'))) {
// Check if pushing to protected branch
for (const branch of PROTECTED_BRANCHES) {
if (command.includes(branch)) {
return {
valid: false,
error: Force push to ${branch} blocked. Use --force-with-lease instead.,
};
}
}
return {
valid: true,
error: '',
warning: 'Force push detected. Ensure you know what you are doing.',
};
}
return { valid: true, error: '' }; }
module.exports = { validate };
Pattern 2: Memory Extractor (Post-Tool)
For extracting learnings after task completion:
'use strict';
/* * Extract Workflow Learnings * Automatically captures patterns from completed workflows /
const fs = require('fs'); const path = require('path');
function findProjectRoot() { let dir = __dirname; while (dir !== path.parse(dir).root) { if (fs.existsSync(path.join(dir, '.claude'))) return dir; dir = path.dirname(dir); } return process.cwd(); }
const LEARNINGS_PATH = path.join(findProjectRoot(), '.claude/context/memory/learnings.md');
function extractLearnings(context) { const { tool, parameters, result } = context;
// Only process completed tasks if (!result || result.status !== 'completed') { return { extracted: false }; }
// Extract patterns from result const learnings = [];
if (result.patterns) { learnings.push(...result.patterns); }
if (result.decisions) { learnings.push(...result.decisions); }
if (learnings.length === 0) { return { extracted: false }; }
// Append to learnings file
const entry = \n## [${new Date().toISOString().split('T')[0]}] ${context.taskName || 'Task'}\n\n;
const content = learnings.map(l => - ${l}).join('\n');
fs.appendFileSync(LEARNINGS_PATH, entry + content + '\n');
return { extracted: true, count: learnings.length }; }
module.exports = { extractLearnings };
Pattern 3: Routing Enforcer (User Prompt)
For enforcing routing protocols:
'use strict';
/* * Router First Enforcer * Ensures all requests go through the Router agent /
function validate(context) { const { prompt, currentAgent } = context;
// Skip if already routed if (currentAgent === 'router') { return { valid: true, error: '' }; }
// Skip slash commands (handled by skill system) if (prompt && prompt.trim().startsWith('/')) { return { valid: true, error: '' }; }
// Suggest routing return { valid: true, error: '', warning: 'Consider using Router to spawn appropriate agent via Task tool.', }; }
module.exports = { validate };
Pattern 4: Session Initializer
For session lifecycle management:
'use strict';
/* * Session Memory Initializer * Reminds agents to read memory files at session start /
const fs = require('fs'); const path = require('path');
function findProjectRoot() { let dir = __dirname; while (dir !== path.parse(dir).root) { if (fs.existsSync(path.join(dir, '.claude'))) return dir; dir = path.dirname(dir); } return process.cwd(); }
const MEMORY_FILES = [ '.claude/context/memory/learnings.md', '.claude/context/memory/issues.md', '.claude/context/memory/decisions.md', ];
function initialize() { const root = findProjectRoot();
console.log('\n' + '='.repeat(50)); console.log(' SESSION MEMORY REMINDER'); console.log('='.repeat(50)); console.log('\n Before starting work, read these memory files:');
for (const file of MEMORY_FILES) {
const fullPath = path.join(root, file);
if (fs.existsSync(fullPath)) {
const stats = fs.statSync(fullPath);
const modified = stats.mtime.toISOString().split('T')[0];
console.log(- ${file} (updated: ${modified}));
}
}
console.log('\n' + '='.repeat(50) + '\n');
return { initialized: true }; }
// Run on direct execution if (require.main === module) { initialize(); }
module.exports = { initialize };
Examples Security Validation Hook node .claude/tools/hook-creator/create-hook.mjs \ --name "secret-detector" \ --type "PreToolUse" \ --purpose "Blocks commits containing secrets or credentials" \ --category "security" \ --matcher "Bash"
Audit Logging Hook node .claude/tools/hook-creator/create-hook.mjs \ --name "operation-logger" \ --type "PostToolUse" \ --purpose "Logs all file modifications to audit trail" \ --category "audit"
Intent Analysis Hook node .claude/tools/hook-creator/create-hook.mjs \ --name "intent-classifier" \ --type "UserPromptSubmit" \ --purpose "Classifies user intent for intelligent routing" \ --category "routing"
Workflow Integration
This skill is part of the unified artifact lifecycle. For complete multi-agent orchestration:
Router Decision: .claude/workflows/core/router-decision.md
How the Router discovers and invokes this skill's artifacts
Artifact Lifecycle: .claude/workflows/core/skill-lifecycle.md
Discovery, creation, update, deprecation phases Version management and registry updates CLAUDE.md integration requirements
External Integration: .claude/workflows/core/external-integration.md
Safe integration of external artifacts Security review and validation phases Cross-Reference: Creator Ecosystem
This skill is part of the Creator Ecosystem. After creating a hook, consider if companion artifacts are needed:
Need Creator to Invoke Command Dedicated skill for hook logic skill-creator Skill({ skill: 'skill-creator' }) Agent that uses this hook agent-creator Skill({ skill: 'agent-creator' }) Workflow for hook testing workflow-creator Create in .claude/workflows/ Schema for hook config schema-creator Create in .claude/schemas/ Template for hook scaffold template-creator Create in .claude/templates/ Integration Workflow
After creating a hook that needs additional capabilities:
// 1. Hook created but needs dedicated skill Skill({ skill: 'skill-creator' }); // Create skill that encapsulates hook logic
// 2. Hook needs to be assigned to agent // Update agent's workflow to include hook awareness
// 3. Hook needs workflow for testing
// Create workflow in .claude/workflows/
Post-Creation Checklist for Ecosystem Integration
After hook is fully created and validated:
[ ] Does hook need a skill wrapper? -> Use skill-creator [ ] Does hook need dedicated agent? -> Use agent-creator [ ] Does hook need testing workflow? -> Create workflow [ ] Should hook be enabled by default? -> Update config.yaml [ ] Does hook interact with other hooks? -> Document in README.md
Iron Laws of Hook Creation
These rules are INVIOLABLE. Breaking them causes silent failures.
- NO HOOK WITHOUT validate() EXPORT
- Every hook MUST export validate() function
-
Hooks without validate() cannot be called programmatically
-
NO HOOK WITHOUT main() FOR CLI
- Every hook MUST have main() for CLI execution
-
Run only when require.main === module
-
NO HOOK WITHOUT GRACEFUL DEGRADATION
- Default to 'warn' mode, not 'block'
- Use environment variables for enforcement mode
-
Never crash on malformed input
-
NO HOOK WITHOUT ERROR HANDLING
- Wrap JSON.parse in try/catch
- Handle missing parameters gracefully
-
Return valid: true when unsure (fail open, not closed)
-
NO HOOK WITHOUT TEST FILE
- Every hook needs
.test.cjs -
Test valid, invalid, and edge cases
-
NO HOOK WITHOUT DOCUMENTATION
- Add to .claude/hooks/README.md
-
Document trigger, behavior, exit codes
-
CROSS-PLATFORM PATHS
- Use path.join() not string concatenation
- Handle both / and \ path separators
-
Use path.normalize() for comparison
-
NO CREATION WITHOUT SYSTEM IMPACT ANALYSIS
- Check if hook requires settings.json registration
- Check if hook requires config.yaml registration
- Check if related hooks need updating
- Document all system changes made
Integration Points Ecosystem Assessor: Hook creator integrates with ecosystem assessment for reverse lookups Agent Creator: Agents can reference hooks in their frontmatter Skill Creator: Skills can define hooks in their hooks/ directory Settings.json: Hooks are auto-registered with proper triggers and matchers Config.yaml: Event hooks registered for UserPromptSubmit, SessionStart, etc. File Placement & Standards Output Location Rules
This skill outputs to: .claude/hooks/
Categories:
safety/ - Safety validators (command validation, security checks) routing/ - Router enforcement hooks memory/ - Memory management hooks session/ - Session lifecycle hooks validation/ - Input/output validation hooks Mandatory References File Placement: See .claude/docs/FILE_PLACEMENT_RULES.md Developer Workflow: See .claude/docs/DEVELOPER_WORKFLOW.md Artifact Naming: See .claude/docs/ARTIFACT_NAMING.md Enforcement
File placement is enforced by file-placement-guard.cjs hook. Invalid placements will be blocked in production mode.
Memory Protocol (MANDATORY)
Before creating a hook:
cat .claude/context/memory/learnings.md
Check for:
Previously created hooks Known hook patterns User preferences for hook behavior
After completing:
New hook created -> Append to .claude/context/memory/learnings.md Issue with hook -> Append to .claude/context/memory/issues.md Hook design decision -> Append to .claude/context/memory/decisions.md
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.