Initial commit
This commit is contained in:
306
skills/skill-developer/HOOK_MECHANISMS.md
Normal file
306
skills/skill-developer/HOOK_MECHANISMS.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# Hook Mechanisms - Deep Dive
|
||||
|
||||
Technical deep dive into how the UserPromptSubmit and PreToolUse hooks work.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [UserPromptSubmit Hook Flow](#userpromptsubmit-hook-flow)
|
||||
- [PreToolUse Hook Flow](#pretooluse-hook-flow)
|
||||
- [Exit Code Behavior (CRITICAL)](#exit-code-behavior-critical)
|
||||
- [Session State Management](#session-state-management)
|
||||
- [Performance Considerations](#performance-considerations)
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
- [SKILL.md](SKILL.md) - Main skill guide
|
||||
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Debug hook issues
|
||||
- [SKILL_RULES_REFERENCE.md](SKILL_RULES_REFERENCE.md) - Configuration reference
|
||||
Reference in New Issue
Block a user