Issue 03 | Hooks Deep Dive β Caveman's Automatic Activation Engine
π― Learning Objectives
By the end of this issue, you will master:
- The four lifecycle events of Claude Code Hooks
- The responsibilities and trigger timings of Caveman's three Hook files
- The bridging principle of the Flag File (
~/.claude/.caveman-active) - How to customize and extend Hook configurations
- Comparison of Hook mechanisms across three major platforms
π Core Concept: Claude Code Hooks Mechanism
3.1 What is a Hook?
A Hook is a deterministic callback mechanism provided by Claude Code. Unlike instructions in CLAUDE.md (which are merely "suggestions"), Hooks are Shell commands or scripts that are guaranteed to execute.
graph LR
subgraph Comparison["Instructions vs Hook"]
direction TB
A["π CLAUDE.md Instructions
ββββββββββββββ
β’ Suggestive
β’ Agent may ignore
β’ Execution not guaranteed"]
B["β‘ Hook Script
ββββββββββββββ
β’ Deterministic
β’ 100% guaranteed to execute
β’ Independent of Agent's will"]
end
A -.->|"May be ignored"| C["Agent Behavior"]
B -->|"Guaranteed to execute"| C3.2 Four Lifecycle Events
Claude Code's Hook system provides four mounting points:
sequenceDiagram
participant U as User
participant CC as Claude Code
participant H as Hook System
Note over CC: π’ SessionStart
CC->>H: Trigger SessionStart hooks
H-->>CC: Inject startup context
loop Each user input
U->>CC: Enter prompt
Note over CC: π UserPromptSubmit
CC->>H: Trigger UserPromptSubmit hooks
loop Each tool call
Note over CC: π§ PreToolUse
CC->>H: Trigger PreToolUse hooks
CC->>CC: Execute tool (Read/Write/Bash...)
Note over CC: β
PostToolUse
CC->>H: Trigger PostToolUse hooks
end
CC->>U: Return response
end| Event | Trigger Timing | Typical Use Case |
|---|---|---|
SessionStart |
Session start (once only) | Inject global rules, initialize state |
UserPromptSubmit |
Each time user sends a message | Detect commands, log events |
PreToolUse |
Before tool call | Security checks, permission interception |
PostToolUse |
After tool call | Log recording, automatic formatting |
π Caveman's Three Hook Files
3.3 caveman-activate.js β SessionStart Hook
This is Caveman's "startup engine". It executes automatically every time a Claude Code session starts.
Responsibilities:
- Writes
fullto the Flag File~/.claude/.caveman-active - Injects Caveman's compression rules as hidden context (stdout β system context)
- Detects if a custom statusLine exists; if not, suggests configuration
Brief Overview of Working Principle:
// caveman-activate.js core logic (simplified)
const fs = require('fs');
const path = require('path');
// 1. Write flag file, marking Caveman as active
const flagPath = path.join(process.env.HOME, '.claude', '.caveman-active');
fs.writeFileSync(flagPath, 'full');
// 2. Output rules to stdout (Claude Code will use it as hidden system context)
console.log(`
Terse like caveman. Technical substance exact. Only fluff die.
Drop: articles, filler (just/really/basically), pleasantries, hedging.
Fragments OK. Short synonyms. Code unchanged.
Pattern: [thing] [action] [reason]. [next step].
ACTIVE EVERY RESPONSE. No revert after many turns.
`);
// 3. Check statusLine configuration
const settingsPath = path.join(process.env.HOME, '.claude', 'settings.json');
if (fs.existsSync(settingsPath)) {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
if (!settings.statusLine) {
console.log('[CAVEMAN] No statusLine configured. Say "setup caveman badge" to add it.');
}
}
π‘ Key Detail: The stdout output of the SessionStart Hook is injected as hidden system contextβClaude can see it, but the user cannot. This is crucial for Caveman's "silent activation".
3.4 caveman-mode-tracker.js β UserPromptSubmit Hook
This is Caveman's "mode tracker". It executes every time the user sends a message.
Responsibilities:
- Detects if the user's message contains the
/cavemancommand - Parses command parameters and writes the new mode to the Flag File
Supported Modes:
/caveman β Writes "full"
/caveman lite β Writes "lite"
/caveman ultra β Writes "ultra"
/caveman wenyan β Writes "wenyan"
/caveman wenyan-lite β Writes "wenyan-lite"
/caveman wenyan-ultra β Writes "wenyan-ultra"
/caveman-commit β Writes "commit"
/caveman-review β Writes "review"
/caveman:compress β Writes "compress"
Core Logic:
// caveman-mode-tracker.js core logic (simplified)
const fs = require('fs');
const path = require('path');
// Read user's message from stdin
const userPrompt = process.argv[2] || '';
const flagPath = path.join(process.env.HOME, '.claude', '.caveman-active');
// Detect /caveman command and update flag file
const match = userPrompt.match(/^\/(caveman(?:-\w+)?)\s*(\w*)/);
if (match) {
const command = match[1]; // "caveman" or "caveman-commit" etc.
const level = match[2]; // "lite", "ultra", "wenyan", etc.
let mode = 'full';
if (command === 'caveman-commit') mode = 'commit';
else if (command === 'caveman-review') mode = 'review';
else if (command === 'caveman:compress') mode = 'compress';
else if (level) mode = level;
fs.writeFileSync(flagPath, mode);
}
3.5 caveman-statusline.sh β Status Bar Script
This is Caveman's "dashboard". It is not a Hook, but an independent script called by Claude Code's statusLine configuration.
Responsibilities:
- Reads the current mode from the Flag File
- Outputs badge text with ANSI colors
#!/bin/bash
# caveman-statusline.sh
caveman_flag="$HOME/.claude/.caveman-active"
if [ -f "$caveman_flag" ]; then
mode=$(cat "$caveman_flag" 2>/dev/null)
if [ "$mode" = "full" ] || [ -z "$mode" ]; then
# Orange [CAVEMAN] badge
echo -e '\033[38;5;172m[CAVEMAN]\033[0m'
else
suffix=$(echo "$mode" | tr '[:lower:]' '[:upper:]')
# Orange [CAVEMAN:ULTRA] etc. badges
echo -e '\033[38;5;172m[CAVEMAN:'"${suffix}"']\033[0m'
fi
fi
Badge Examples:
| Command | Status Bar Display |
|---|---|
/caveman |
[CAVEMAN] |
/caveman ultra |
[CAVEMAN:ULTRA] |
/caveman wenyan |
[CAVEMAN:WENYAN] |
/caveman-commit |
[CAVEMAN:COMMIT] |
/caveman-review |
[CAVEMAN:REVIEW] |
π Flag File Bridging Mechanism
The three Hook components coordinate their state via a simple text file:
graph LR
A["SessionStart Hook
caveman-activate.js"] -->|"Writes 'full'"| F["~/.claude/.caveman-active"]
B["UserPromptSubmit Hook
caveman-mode-tracker.js"] -->|"Writes new mode"| F
F -->|"Reads mode"| C["Statusline Script
caveman-statusline.sh"]
C --> D["Displayed in status bar
[CAVEMAN:ULTRA]"]
style F fill:#FFD700,stroke:#B8860B,color:#000This is a classic file-as-protocol design pattern:
- No Inter-Process Communication (IPC)
- No Sockets or HTTP
- Only a plain text file
- Simple, reliable, zero-dependency
π Manually Configure settings.json
If you need to manually configure Hooks (instead of automatic installation via a Plugin), you need to edit ~/.claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"command": "node /path/to/caveman-activate.js"
}
],
"UserPromptSubmit": [
{
"matcher": "",
"command": "node /path/to/caveman-mode-tracker.js \"$PROMPT\""
}
]
},
"statusLine": {
"type": "command",
"command": "bash /path/to/caveman-statusline.sh"
}
}
β οΈ Replace
/path/to/with the actual path. Plugin installation handles paths automatically.
If you already have a custom statusLine script, merge the following code into your existing script:
# Add to your custom statusline script
caveman_text=""
caveman_flag="$HOME/.claude/.caveman-active"
if [ -f "$caveman_flag" ]; then
caveman_mode=$(cat "$caveman_flag" 2>/dev/null)
if [ "$caveman_mode" = "full" ] || [ -z "$caveman_mode" ]; then
caveman_text=$'\033[38;5;172m[CAVEMAN]\033[0m'
else
caveman_suffix=$(echo "$caveman_mode" | tr '[:lower:]' '[:upper:]')
caveman_text=$'\033[38;5;172m[CAVEMAN:'"${caveman_suffix}"$']\033[0m'
fi
fi
# Append $caveman_text to your status bar output
echo "$your_existing_status $caveman_text"
π Comparison of Hook Capabilities Across Three Major Platforms
| Hook Capability | Claude Code | Antigravity | Gemini CLI |
|---|---|---|---|
| SessionStart | β Native support | β No Hook system | β οΈ Simulated via GEMINI.md |
| UserPromptSubmit | β Native support | β Not supported | β Not supported |
| PreToolUse | β Native support | β Not supported | β Not supported |
| PostToolUse | β Native support | β Not supported | β Not supported |
| Flag File State Tracking | β Full support | β Not needed (no Hook) | β Not needed |
| Statusline Badge | β Native support | β Not supported | β Not supported |
| Auto-activation Method | Hook β stdout injection | GEMINI.md rule file | GEMINI.md context file |
| Mode Switching Method | Hook intercepts /caveman | Natural language | /caveman command |
π‘ Conclusion: Claude Code possesses the most complete Hook ecosystem, which is why Caveman was originally designed for it. Although Antigravity and Gemini CLI lack a Hook system, they can achieve core functionalities through rule files and context injection.
π Key Takeaways from This Issue
- Hook β Suggestion: Hooks are deterministically executed scripts, CLAUDE.md are suggestive instructions
- Caveman uses three Hooks in collaboration: activate(startup injection) + mode-tracker(mode switching) + statusline(status display)
- The Flag File (
~/.claude/.caveman-active) acts as a bridge between the three, storing the current mode - The stdout of the SessionStart Hook is injected as hidden system contextβusers cannot see it, but Claude can
- Antigravity and Gemini CLI lack a Hook system, achieving equivalent functionality through rule files