Issue 03 | Hooks Deep Dive β€” Caveman's Automatic Activation Engine

⏱ Est. reading time: 17 min Updated on 5/7/2026

🎯 Learning Objectives

By the end of this issue, you will master:

  1. The four lifecycle events of Claude Code Hooks
  2. The responsibilities and trigger timings of Caveman's three Hook files
  3. The bridging principle of the Flag File (~/.claude/.caveman-active)
  4. How to customize and extend Hook configurations
  5. 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"| C

3.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:

  1. Writes full to the Flag File ~/.claude/.caveman-active
  2. Injects Caveman's compression rules as hidden context (stdout β†’ system context)
  3. 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 /caveman command
  • 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:#000

This 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

  1. Hook β‰  Suggestion: Hooks are deterministically executed scripts, CLAUDE.md are suggestive instructions
  2. Caveman uses three Hooks in collaboration: activate(startup injection) + mode-tracker(mode switching) + statusline(status display)
  3. The Flag File (~/.claude/.caveman-active) acts as a bridge between the three, storing the current mode
  4. The stdout of the SessionStart Hook is injected as hidden system contextβ€”users cannot see it, but Claude can
  5. Antigravity and Gemini CLI lack a Hook system, achieving equivalent functionality through rule files

πŸ”— References