# How ponytail-mode-tracker.js Handles Command Detection and Mode Switching

> Discover how ponytail-mode-tracker.js detects commands with regex and manages mode switching through state functions. Learn how it parses JSON input and outputs to AI runtimes.

- Repository: [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail)
- Tags: internals
- Published: 2026-08-27

---

**The [`ponytail-mode-tracker.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mode-tracker.js) hook parses JSON input from stdin, detects commands using the regex `/^[/@$]ponytail/`, and orchestrates mode transitions via discrete state management functions while emitting formatted output to the active AI runtime.**

The [`hooks/ponytail-mode-tracker.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-mode-tracker.js) file serves as the core **UserPromptSubmit** handler in the DietrichGebert/ponytail repository, bridging user input with the framework's multi-mode architecture. This lightweight JavaScript module determines whether a prompt contains valid Ponytail directives, manages the lifecycle of active modes, and ensures consistent behavior across Claude Code, Codex, and Qoder environments.

## Input Parsing and Payload Extraction

The hook begins by consuming raw JSON from **stdin**, applying defensive preprocessing to ensure cross-platform reliability.

**BOM stripping** occurs first via `input.replace(/^\uFEFF/, '')`, eliminating UTF-8 byte-order marks that some shells prepend to piped input. After sanitization, the script parses the JSON payload and extracts the `prompt` field, converting it to lowercase for case-insensitive command matching.

```javascript
// Standard input processing pipeline
const input = fs.readFileSync(0, 'utf8').replace(/^\uFEFF/, '');
const payload = JSON.parse(input);
const prompt = (payload.prompt || '').toLowerCase();

```

On Windows environments, the implementation includes a timed fallback using `setTimeout(...).unref()` to prevent process hangs when stdin never emits an `end` event.

## Command Detection and Validation

The module employs a two-tier detection strategy to distinguish Ponytail commands from ordinary prompts.

**Primary command detection** uses the regular expression `/^[/@$]ponytail/` to identify strings beginning with `/ponytail`, `@ponytail`, or `$ponytail`. Upon match, the hook splits the prompt into constituent parts: the trigger symbol, the sub-command (`arg`), and optional additional parameters.

If the primary pattern fails, the system falls back to the **deactivation detection** layer, invoking `isDeactivationCommand` from [`ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-config.js). This utility matches natural language phrases like "stop ponytail" or "normal mode" against the full prompt text, ensuring whole-message matching to prevent accidental toggling when these phrases appear embedded within conversational text.

```javascript
// Command parsing logic (simplified representation)
const ponytailRegex = /^[/@$]ponytail/;
if (ponytailRegex.test(prompt)) {
    const parts = prompt.split(/\s+/);
    const cmd = parts[0];  // e.g., "/ponytail"
    const arg = parts[1];  // e.g., "full", "lite", "off"
}

```

## Mode Switching and State Management

Once validated, commands trigger specific state transitions through the **ponytail-runtime.js** abstraction layer, which manages the `.ponytail-active` flag file and configuration persistence.

### Session-Scoped Mode Changes

Direct mode switches (`lite`, `full`, `ultra`, `off`, `review`) invoke `setMode(mode)`, setting `modeSwitched = true` and writing a confirmation via `writeHookOutput`. These changes affect only the current session, stored temporarily in the runtime state.

### Persisted Default Configuration

The `/ponytail default <mode>` syntax triggers `writeDefaultMode(dmode)`, persisting the selection to `$XDG_CONFIG_HOME/ponytail/config.json` (or the OS-specific fallback). This establishes the baseline mode for future sessions.

### Review and Deactivation Paths

The `review` sub-command activates a special session-only mode that never persists to disk, while deactivation commands invoke `clearMode()` to remove the active flag entirely.

```javascript
// Example: Persisting a default mode
if (arg === 'default' && dmode) {
    writeDefaultMode(dmode);
    writeHookOutput(`Default mode set to ${dmode}`);
}

```

## Runtime Integration and Qoder Specialization

For standard runtimes (Claude Code, Codex), the hook outputs simple status messages like `PONYTAIL MODE CHANGED — level: full`.

**Qoder environments** receive enhanced handling: when `isQoder` evaluates true, the hook injects the complete instruction set generated by `getPonytailInstructions` from [`ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-instructions.js). This prepends the full Ponytail rule set to every prompt, folding mode-change confirmations into the ruleset header rather than emitting separate status lines.

## Error Handling and Edge Cases

The implementation includes several defensive mechanisms:

- **Invalid command fallback**: Unrecognized arguments default to `getDefaultMode()` from [`ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-config.js)
- **Report-only queries**: A bare `/ponytail` command (no arguments) triggers `readMode()` to display the current active level without mutation
- **Stateless architecture**: All persistent state resides in external files (`.ponytail-active` and [`config.json`](https://github.com/DietrichGebert/ponytail/blob/main/config.json)), making the hook resilient to process restarts

## Summary

- **[`ponytail-mode-tracker.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mode-tracker.js)** serves as the central command interpreter, reading JSON from stdin and detecting directives via `/^[/@$]ponytail/`
- **Command parsing** separates triggers from sub-commands (`full`, `lite`, `off`, `default`, `review`) and handles natural language deactivation phrases
- **Mode switching** relies on [`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js) functions (`setMode`, `clearMode`, `readMode`) to manage the `.ponytail-active` state file
- **Persistence** occurs only when users explicitly set defaults via `writeDefaultMode`, while session changes remain transient
- **Qoder integration** differs from standard runtimes by injecting full instruction sets rather than emitting simple status messages

## Frequently Asked Questions

### How does ponytail-mode-tracker.js detect if a prompt contains a Ponytail command?

The hook applies the regular expression `/^[/@$]ponytail/` to the lowercase prompt string to identify commands prefixed with `/ponytail`, `@ponytail`, or `$ponytail`. If this fails, it checks for deactivation phrases like "stop ponytail" using the `isDeactivationCommand` utility from [`ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-config.js), which requires whole-message matches to prevent accidental triggers.

### What is the difference between session-scoped and persisted mode switching in Ponytail?

Session-scoped changes (e.g., `/ponytail full`) call `setMode()` and affect only the current conversation, storing state in the transient `.ponytail-active` file. Persisted changes (e.g., `/ponytail default ultra`) invoke `writeDefaultMode()` to update [`config.json`](https://github.com/DietrichGebert/ponytail/blob/main/config.json), establishing a default that survives across restarts and new sessions.

### How does the hook handle different AI runtimes like Claude, Codex, and Qoder?

For Claude and Codex, the hook emits simple text confirmations via `writeHookOutput`. For Qoder (`isQoder === true`), it prepends the complete instruction set from [`ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-instructions.js) to every prompt, integrating mode-change notifications into the ruleset header rather than generating separate output lines.

### What happens if a user enters an invalid Ponytail sub-command?

Unrecognized arguments fall back to the system's default mode resolution via `getDefaultMode()`. The hook treats invalid inputs as report-only queries, displaying the current active mode without performing state transitions, ensuring the system remains stable despite malformed commands.