How the UserPromptSubmit Hook Manages Caveman Activation in Claude Code
The UserPromptSubmit hook intercepts every prompt submission via src/hooks/caveman-mode-tracker.js to parse Caveman commands, guard against scheduled tasks, and persist activation states to $CLAUDE_CONFIG_DIR/.caveman-active while maintaining graceful degradation for missing dependencies.
The UserPromptSubmit hook serves as the primary control plane for Caveman mode activation in the JuliusBrussee/caveman repository. This hook bridges user input—whether slash commands or natural language triggers—and the persistent state that drives Claude Code's behavioral modifications. Operating within Claude Code's 5-second execution budget, it ensures mode changes take effect immediately upon prompt submission without requiring session restarts.
Core Architecture and Event Handling
The UserPromptSubmit hook is implemented in src/hooks/caveman-mode-tracker.js and executes synchronously for every UserPromptSubmit event emitted by Claude Code. It reads the first complete JSON payload from stdin (containing prompt, cwd, and optional transcript_path) rather than waiting for EOF, ensuring compliance with host time constraints.
Defensive Module Loading with requireSibling
Before processing payloads, the hook establishes dependencies using a defensive requireSibling helper. This utility attempts to load caveman-config.js and caveman-parse.js from sibling directories. If either file is missing or exports an unexpected shape, the hook degrades gracefully by stubbing required functions, preventing fatal MODULE_NOT_FOUND cascades.
// Defensive loading pattern from caveman-mode-tracker.js
const config = requireSibling('caveman-config.js', {
getDefaultMode: () => 'full',
safeWriteFlag: () => {},
recordModeChange: () => {}
});
Command Parsing and Normalization
Reconstructing Slash Command Envelopes
Claude Code wraps slash commands in XML-like tags. The UserPromptSubmit hook extracts <command-name> and <command-args> using regex patterns to reconstruct the canonical /caveman <args> format before downstream parsing.
// ── Extract and normalize the incoming prompt ──
const data = JSON.parse(raw);
let prompt = (data.prompt || '').trim().toLowerCase().replace(/\s+/g, ' ');
// ── Re‑build slash‑command envelope ──
const envName = /<command-name>\s*([^<\s]+)\s*<\/command-name>/.exec(prompt);
if (envName && envName[1].startsWith('/caveman')) {
const envArgs = /<command-args>\s*([^<]*?)\s*<\/command-args>/.exec(prompt);
const args = envArgs ? envArgs[1].trim() : '';
prompt = args ? envName[1] + ' ' + args : envName[1];
}
Scheduled Task Guard Clause
When the prompt contains a <scheduled-task …> marker, the hook aborts early, leaving the active flag untouched and emitting no reinforcement. This prevents automated background tasks from inadvertently triggering mode changes.
Mode Activation and State Persistence
The parseModeChange Decision Engine
The hook delegates command interpretation to parseModeChange from caveman-parse.js, passing the current default mode via getDefaultMode and a flag indicating whether to skip natural-language triggers. The function returns an object specifying whether to activate, deactivate, or ignore the mode change.
// ── Parse mode change and write the flag ──
const change = parseModeChange(prompt, { getDefaultMode, skipNaturalLanguage });
if (change && change.action === 'activate') {
safeWriteFlag(change.mode); // writes $CLAUDE_CONFIG_DIR/.caveman-active
recordModeChange(change.mode, true); // logs the transition
}
Persistent Flag Writing with safeWriteFlag
Upon activation, the hook writes the target mode to $CLAUDE_CONFIG_DIR/.caveman-active using the safeWriteFlag function from caveman-config.js. This file serves as the source of truth for the caveman-activate.js session-start hook, which reads it to emit appropriate rulesets.
One-Shot Mode Handling
For independent modes (commit, review, compress), the hook stores the previous prose mode in .caveman-active.prev before activation. This enables automatic restoration of the prior state after the specific task completes, distinguishing temporary context switches from persistent mode changes.
Special Commands and Error Resilience
The /caveman-stats Child Process
When detecting /caveman-stats commands, the hook spawns src/hooks/caveman-stats.js as a child process with a 2.5-second timeout. It returns a JSON payload containing hookSpecificOutput with hookEventName: "UserPromptSubmit" and the statistics block as additionalContext.
Graceful Degradation for Corrupted Installations
If caveman-config.js cannot be loaded, the hook falls back to a minimal stub that always returns the built-in default mode 'full' and no-ops for flag writes. This ensures the hook never crashes, even in partially broken installations, and always returns valid JSON within the execution budget.
Summary
- The UserPromptSubmit hook in
src/hooks/caveman-mode-tracker.jsintercepts every Claude Code prompt to evaluate Caveman activation commands. - It uses defensive loading via
requireSiblingto prevent crashes when configuration modules are missing or corrupted. - The hook reconstructs slash commands from XML envelopes and implements a guard against scheduled task interference.
- Mode changes are persisted to
$CLAUDE_CONFIG_DIR/.caveman-activeusingsafeWriteFlag, with special handling for one-shot modes requiring state restoration. - Graceful degradation ensures valid output even when
caveman-config.jsis unavailable, defaulting to mode'full'.
Frequently Asked Questions
What file implements the UserPromptSubmit hook?
The UserPromptSubmit hook is implemented in src/hooks/caveman-mode-tracker.js. This file handles the UserPromptSubmit event emitted by Claude Code whenever a user submits a prompt, parsing the JSON payload from stdin to detect Caveman commands.
How does the hook handle missing configuration files?
The hook uses a requireSibling helper that stubs missing dependencies rather than throwing MODULE_NOT_FOUND errors. If caveman-config.js cannot be loaded, it falls back to returning the default mode 'full' and no-op functions for flag operations, ensuring the hook always completes within the 5-second budget.
What distinguishes one-shot modes from regular modes in Caveman activation?
Regular modes persist until explicitly changed, while one-shot modes (commit, review, compress) store the previous mode in .caveman-active.prev before activation. This allows the system to restore the prior state automatically after the specific operation completes, making them temporary context switches rather than persistent configuration changes.
How does the UserPromptSubmit hook process slash commands wrapped in XML?
Claude Code wraps slash commands in XML-like tags such as <command-name> and <command-args>. The hook uses regex extraction to parse these tags, reconstructs the command into standard /caveman <args> format, and passes the normalized string to parseModeChange in caveman-parse.js for evaluation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →