# How Ponytail Detects and Suggests Statusline Configuration in Claude Code

> Learn how Ponytail detects statusline configuration by parsing settings.json and suggests shell-safe commands to display active mode badges in your Claude terminal prompt. Boost your productivity.

- Repository: [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail)
- Tags: how-to-guide
- Published: 2026-08-30

---

**Ponytail detects missing statusline configuration by parsing Claude's [`settings.json`](https://github.com/DietrichGebert/ponytail/blob/main/settings.json) during session initialization and suggests a shell-safe command snippet that displays the active mode badge directly in the terminal prompt.**

The `DietrichGebert/ponytail` plugin enhances Claude Code sessions by displaying the current operational mode (such as `[PONYTAIL]` or `[PONYTAIL:ULTRA]`) in the terminal statusline. To ensure users benefit from this visual indicator, the plugin automatically detects whether statusline configuration is present and suggests the appropriate setup command if missing.

## The Session Initialization Hook

When a Claude Code session begins, the **[`ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-activate.js)** hook executes automatically. This script performs three critical operations: writing the active mode flag to `~/.claude/.ponytail-active`, emitting mode-specific instructions, and scanning for existing statusline configuration in **lines 45-87**.

### Reading Claude's settings.json

The detection logic begins by locating and parsing Claude's configuration file. At **lines 47-52**, the hook constructs the path to `~/.claude/settings.json` and attempts to read it:

```javascript
const fs = require('fs');
const path = require('path');
const os = require('os');

const claudeDir = path.join(os.homedir(), '.claude');
const settingsPath = path.join(claudeDir, 'settings.json');

if (fs.existsSync(settingsPath)) {
  const content = fs.readFileSync(settingsPath, 'utf8').replace(/^\uFEFF/, '');
  const settings = JSON.parse(content);
  // Detection logic continues...
}

```

The code strips the UTF-8 BOM (Byte Order Mark) using `.replace(/^\uFEFF/, '')` before parsing to handle files created on Windows systems.

### Checking for Existing Configuration

After parsing, the hook checks for a `statusLine` property at **lines 51-53**:

```javascript
let hasStatusline = false;
if (settings.statusLine) {
  hasStatusline = true;
}

```

If `settings.statusLine` exists, the plugin assumes the user has already configured a custom statusline and skips the suggestion logic entirely.

## Preventing Repeated Prompts

To avoid annoying users with persistent notifications, Ponytail implements a **nudge-once guard**. Before displaying any suggestion, the hook checks for a hidden flag file at `.ponytail-statusline-nudged` within the Claude configuration directory (**lines 59-62**):

```javascript
const nudgeFlagPath = path.join(claudeDir, '.ponytail-statusline-nudged');

if (!hasStatusline && !fs.existsSync(nudgeFlagPath)) {
  // Show suggestion and create flag file
  fs.writeFileSync(nudgeFlagPath, '');
}

```

This ensures the setup prompt appears only during the first session where a missing configuration is detected, preventing repeated interruptions on subsequent launches.

## Validating Path Safety with isShellSafe

Before embedding the statusline script path into a command string, Ponytail validates shell safety using the **`isShellSafe`** function defined in [`hooks/ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-config.js) (**lines 50-52**):

```javascript
function isShellSafe(str) {
  return /^[A-Za-z0-9 _.\-:/\\~]+$/.test(str);
}

```

This whitelist regex permits only alphanumeric characters, spaces, underscores, dots, hyphens, colons, forward slashes, backslashes, and tildes. If the installation path contains special characters like `$`, backticks, or quotes, the function returns `false`, triggering manual configuration instructions instead of automatic embedding to prevent command injection.

## Generating the Statusline Suggestion

Based on the safety check and operating system, the hook constructs the appropriate command at **lines 70-85**. For Unix systems with safe paths, it generates a command pointing to [`ponytail-statusline.sh`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-statusline.sh):

```javascript
const scriptPath = path.join(__dirname, 'ponytail-statusline.sh');
const command = `bash "${scriptPath}"`;
const suggestion = {
  statusLine: {
    type: "command",
    command: command
  }
};

```

If the path fails the safety check, the hook outputs a warning directing users to manually add the statusline configuration to `~/.claude/settings.json`, explaining that automatic embedding is unsafe due to special characters in the installation path.

## The Statusline Script Implementation

The **[`ponytail-statusline.sh`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-statusline.sh)** script (referenced in the generated command) reads the active mode flag and outputs a colored badge:

```bash
#!/usr/bin/env bash
flag="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.ponytail-active"
[ -f "$flag" ] || exit 0

mode=$(head -n1 "$flag" | tr -d '[:space:]')
color=108               # green for normal modes

[ "$mode" = "ultra" ] && color=173   # amber for ultra mode

if [ -z "$mode" ] || [ "$mode" = "full" ]; then
    printf '\033[38;5;%sm[PONYTAIL]\033[0m' "$color"
else
    printf '\033[38;5;%sm[PONYTAIL:%s]\033[0m' "$color" "$(printf '%s' "$mode" | tr '[:lower:]' '[:upper:]')"
fi

```

This script outputs ANSI-colored text displaying `[PONYTAIL]` for standard modes or `[PONYTAIL:ULTRA]` when ultra mode is active, using green (color 108) or amber (color 173) respectively.

## Complete Detection Logic Example

Here is a runnable Node.js script that simulates Ponytail's detection and suggestion logic:

```javascript
const fs = require('fs');
const path = require('path');
const os = require('os');

// Simulated isShellSafe from ponytail-config.js
function isShellSafe(str) {
  return /^[A-Za-z0-9 _.\-:/\\~]+$/.test(str);
}

function suggestStatusline(claudeDir) {
  const settingsPath = path.join(claudeDir, 'settings.json');
  if (!fs.existsSync(settingsPath)) return null;

  const raw = fs.readFileSync(settingsPath, 'utf8').replace(/^\uFEFF/, '');
  const settings = JSON.parse(raw);
  if (settings.statusLine) return null; // already configured

  const scriptName = process.platform === 'win32'
    ? 'ponytail-statusline.ps1' : 'ponytail-statusline.sh';
  const scriptPath = path.join(__dirname, scriptName);

  if (isShellSafe(scriptPath)) {
    const cmd = process.platform === 'win32'
      ? `powershell -ExecutionPolicy Bypass -File "${scriptPath}"`
      : `bash "${scriptPath}"`;
    return {
      statusLine: { type: 'command', command: cmd },
      message: 'Add the above statusLine entry to your settings.json'
    };
  }
  return { message: 'Install path unsafe – configure statusLine manually.' };
}

// Usage example
const claudeDir = path.join(os.homedir(), '.claude');
console.log(suggestStatusline(claudeDir));

```

## Summary

- **Detection Entry Point**: The [`ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-activate.js) hook runs at every Claude session start, reading `~/.claude/settings.json` to check for existing `statusLine` configuration.
- **BOM Handling**: The parser strips UTF-8 Byte Order Marks before JSON parsing to ensure cross-platform compatibility with Windows-created files.
- **Nudge Prevention**: A hidden `.ponytail-statusline-nudged` flag file ensures users see the setup suggestion only once, preventing repetitive prompts.
- **Security Validation**: The `isShellSafe` regex whitelist `/^[A-Za-z0-9 _.\-:/\\~]+$/` prevents command injection by validating script paths before embedding them in JSON configuration strings.
- **Platform Support**: The system generates appropriate commands for both Unix ([`ponytail-statusline.sh`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-statusline.sh)) and Windows (`ponytail-statusline.ps1`) environments based on `process.platform`.

## Frequently Asked Questions

### What file does Ponytail check to detect statusline configuration?

Ponytail checks `~/.claude/settings.json` (or `%USERPROFILE%\.claude\settings.json` on Windows) for a `statusLine` property. According to the source code in [`hooks/ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-activate.js), if `settings.statusLine` exists, the plugin assumes the user has already configured a statusline and does not display setup suggestions.

### How does Ponytail prevent showing the same suggestion multiple times?

The plugin creates a hidden flag file named `.ponytail-statusline-nudged` in the Claude configuration directory the first time it suggests statusline setup. On subsequent sessions, the hook checks for this file at **line 59** and skips the suggestion logic if the file exists, ensuring the nudge appears only once.

### What makes a file path "unsafe" for automatic configuration embedding?

Paths containing characters outside the whitelist regex `/^[A-Za-z0-9 _.\-:/\\~]+$/` are considered unsafe. This includes shell metacharacters like `$`, backticks, quotes, semicolons, or ampersands that could enable command injection if embedded directly into JSON configuration strings, as implemented in [`hooks/ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-config.js).

### Does Ponytail automatically modify my settings.json file?

No, Ponytail only detects the missing configuration and suggests the appropriate JSON snippet. It outputs a ready-to-paste configuration for safe paths or manual instructions for unsafe paths, but never writes to [`settings.json`](https://github.com/DietrichGebert/ponytail/blob/main/settings.json) automatically. The separate [`scripts/uninstall.js`](https://github.com/DietrichGebert/ponytail/blob/main/scripts/uninstall.js) script only removes Ponytail-specific entries during uninstallation, leaving user-added configurations intact.