# How the UserPromptSubmit Hook Manages Caveman Activation in Claude Code

> Discover how the UserPromptSubmit hook in JuliusBrussee/caveman manages Caveman activation by parsing commands and persisting states to .caveman-active.

- Repository: [Julius Brussee/caveman](https://github.com/JuliusBrussee/caveman)
- Tags: internals
- Published: 2026-08-22

---

**The UserPromptSubmit hook intercepts every prompt submission via [`src/hooks/caveman-mode-tracker.js`](https://github.com/JuliusBrussee/caveman/blob/main/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`](https://github.com/JuliusBrussee/caveman/blob/main/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`](https://github.com/JuliusBrussee/caveman/blob/main/caveman-config.js) and [`caveman-parse.js`](https://github.com/JuliusBrussee/caveman/blob/main/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.

```javascript
// 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.

```javascript
// ── 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`](https://github.com/JuliusBrussee/caveman/blob/main/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.

```javascript
// ── 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`](https://github.com/JuliusBrussee/caveman/blob/main/caveman-config.js). This file serves as the source of truth for the [`caveman-activate.js`](https://github.com/JuliusBrussee/caveman/blob/main/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`](https://github.com/JuliusBrussee/caveman/blob/main/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`](https://github.com/JuliusBrussee/caveman/blob/main/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.js`](https://github.com/JuliusBrussee/caveman/blob/main/src/hooks/caveman-mode-tracker.js) intercepts every Claude Code prompt to evaluate Caveman activation commands.
- It uses **defensive loading** via `requireSibling` to 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-active` using `safeWriteFlag`, with special handling for **one-shot modes** requiring state restoration.
- **Graceful degradation** ensures valid output even when [`caveman-config.js`](https://github.com/JuliusBrussee/caveman/blob/main/caveman-config.js) is 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`](https://github.com/JuliusBrussee/caveman/blob/main/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`](https://github.com/JuliusBrussee/caveman/blob/main/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`](https://github.com/JuliusBrussee/caveman/blob/main/caveman-parse.js) for evaluation.