Files
gh-jnlei-claude-tools-plugi…/skills/skill-developer/HOOK_MECHANISMS.md
2025-11-30 08:28:15 +08:00

7.7 KiB

Hook Mechanisms - Deep Dive

Technical deep dive into how the UserPromptSubmit and PreToolUse hooks work.

Table of Contents


UserPromptSubmit Hook Flow

Execution Sequence

User submits prompt
    ↓
.claude/settings.json registers hook
    ↓
skill-activation-prompt.sh executes
    ↓
npx tsx skill-activation-prompt.ts
    ↓
Hook reads stdin (JSON with prompt)
    ↓
Loads skill-rules.json
    ↓
Matches keywords + intent patterns
    ↓
Groups matches by priority (critical → high → medium → low)
    ↓
Outputs formatted message to stdout
    ↓
stdout becomes context for Claude (injected before prompt)
    ↓
Claude sees: [skill suggestion] + user's prompt

Key Points

  • Exit code: Always 0 (allow)
  • stdout: → Claude's context (injected as system message)
  • Timing: Runs BEFORE Claude processes prompt
  • Behavior: Non-blocking, advisory only
  • Purpose: Make Claude aware of relevant skills

Input Format

{
  "session_id": "abc-123",
  "transcript_path": "/path/to/transcript.json",
  "cwd": "/root/git/your-project",
  "permission_mode": "normal",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "how does the layout system work?"
}

Output Format (to stdout)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🎯 SKILL ACTIVATION CHECK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📚 RECOMMENDED SKILLS:
  → project-catalog-developer

ACTION: Use Skill tool BEFORE responding
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Claude sees this output as additional context before processing the user's prompt.


PreToolUse Hook Flow

Execution Sequence

Claude calls Edit/Write tool
    ↓
.claude/settings.json registers hook (matcher: Edit|Write)
    ↓
skill-verification-guard.sh executes
    ↓
npx tsx skill-verification-guard.ts
    ↓
Hook reads stdin (JSON with tool_name, tool_input)
    ↓
Loads skill-rules.json
    ↓
Checks file path patterns (glob matching)
    ↓
Reads file for content patterns (if file exists)
    ↓
Checks session state (was skill already used?)
    ↓
Checks skip conditions (file markers, env vars)
    ↓
IF MATCHED AND NOT SKIPPED:
  Update session state (mark skill as enforced)
  Output block message to stderr
  Exit with code 2 (BLOCK)
ELSE:
  Exit with code 0 (ALLOW)
    ↓
IF BLOCKED:
  stderr → Claude sees message
  Edit/Write tool does NOT execute
  Claude must use skill and retry
IF ALLOWED:
  Tool executes normally

Key Points

  • Exit code 2: BLOCK (stderr → Claude)
  • Exit code 0: ALLOW
  • Timing: Runs BEFORE tool execution
  • Session tracking: Prevents repeated blocks in same session
  • Fail open: On errors, allows operation (don't break workflow)
  • Purpose: Enforce critical guardrails

Input Format

{
  "session_id": "abc-123",
  "transcript_path": "/path/to/transcript.json",
  "cwd": "/root/git/your-project",
  "permission_mode": "normal",
  "hook_event_name": "PreToolUse",
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/root/git/your-project/form/src/services/user.ts",
    "old_string": "...",
    "new_string": "..."
  }
}

Output Format (to stderr when blocked)

⚠️ BLOCKED - Database Operation Detected

📋 REQUIRED ACTION:
1. Use Skill tool: 'database-verification'
2. Verify ALL table and column names against schema
3. Check database structure with DESCRIBE commands
4. Then retry this edit

Reason: Prevent column name errors in Prisma queries
File: form/src/services/user.ts

💡 TIP: Add '// @skip-validation' comment to skip future checks

Claude receives this message and understands it needs to use the skill before retrying the edit.


Exit Code Behavior (CRITICAL)

Exit Code Reference Table

Exit Code stdout stderr Tool Execution Claude Sees
0 (UserPromptSubmit) → Context → User only N/A stdout content
0 (PreToolUse) → User only → User only Proceeds Nothing
2 (PreToolUse) → User only CLAUDE BLOCKED stderr content
Other → User only → User only Blocked Nothing

Why Exit Code 2 Matters

This is THE critical mechanism for enforcement:

  1. Only way to send message to Claude from PreToolUse
  2. stderr content is "fed back to Claude automatically"
  3. Claude sees the block message and understands what to do
  4. Tool execution is prevented
  5. Critical for enforcement of guardrails

Example Conversation Flow

User: "Add a new user service with Prisma"

Claude: "I'll create the user service..."
    [Attempts to Edit form/src/services/user.ts]

PreToolUse Hook: [Exit code 2]
    stderr: "⚠️ BLOCKED - Use database-verification"

Claude sees error, responds:
    "I need to verify the database schema first."
    [Uses Skill tool: database-verification]
    [Verifies column names]
    [Retries Edit - now allowed (session tracking)]

Session State Management

Purpose

Prevent repeated nagging in the same session - once Claude uses a skill, don't block again.

State File Location

.claude/hooks/state/skills-used-{session_id}.json

State File Structure

{
  "skills_used": [
    "database-verification",
    "error-tracking"
  ],
  "files_verified": []
}

How It Works

  1. First edit of file with Prisma:

    • Hook blocks with exit code 2
    • Updates session state: adds "database-verification" to skills_used
    • Claude sees message, uses skill
  2. Second edit (same session):

    • Hook checks session state
    • Finds "database-verification" in skills_used
    • Exits with code 0 (allow)
    • No message to Claude
  3. Different session:

    • New session ID = new state file
    • Hook blocks again

Limitation

The hook cannot detect when the skill is actually invoked - it just blocks once per session per skill. This means:

  • If Claude doesn't use the skill but makes a different edit, it won't block again
  • Trust that Claude follows the instruction
  • Future enhancement: detect actual Skill tool usage

Performance Considerations

Target Metrics

  • UserPromptSubmit: < 100ms
  • PreToolUse: < 200ms

Performance Bottlenecks

  1. Loading skill-rules.json (every execution)

    • Future: Cache in memory
    • Future: Watch for changes, reload only when needed
  2. Reading file content (PreToolUse)

    • Only when contentPatterns configured
    • Only if file exists
    • Can be slow for large files
  3. Glob matching (PreToolUse)

    • Regex compilation for each pattern
    • Future: Compile once, cache
  4. Regex matching (Both hooks)

    • Intent patterns (UserPromptSubmit)
    • Content patterns (PreToolUse)
    • Future: Lazy compile, cache compiled regexes

Optimization Strategies

Reduce patterns:

  • Use more specific patterns (fewer to check)
  • Combine similar patterns where possible

File path patterns:

  • More specific = fewer files to check
  • Example: form/src/services/** better than form/**

Content patterns:

  • Only add when truly necessary
  • Simpler regex = faster matching

Related Files: