# How Ponytail Activates on Claude Code, Codex, and Copilot: SessionStart Hook Explained

> Discover how Ponytail activates on Claude Code, Codex, and Copilot using the SessionStart hook. Learn how it injects prompt rulesets for enhanced AI coding assistance.

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

---

**Ponytail activates by detecting the host environment via environment variables, executing a unified SessionStart hook that persists a mode flag to `~/.claude/.ponytail-active`, and emitting host-specific payloads that inject prompt rulesets into Claude Code, Codex, or Microsoft Copilot sessions.**

Ponytail is a prompt-engineering plugin that hooks into the session-start lifecycle of AI coding assistants. According to the DietrichGebert/ponytail source code, the activation flow relies on just two core files—[`hooks/ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-activate.js) and [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js)—to abstract away the differences between Anthropic’s Claude Code, OpenAI’s legacy Codex, and Microsoft Copilot.

## Host Detection via Environment Variables

Activation begins in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js), which inspects **environment variables** to determine the runtime host. The logic checks for `COPILOT_PLUGIN_DATA`, `CLAUDE_PLUGIN_ROOT`, `PLUGIN_DATA`, and `QODER_SESSION_ID` to set boolean flags that dictate output formatting.

```javascript
// hooks/ponytail-runtime.js
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA) ||
  isVsCodeCopilotRoot(process.env.CLAUDE_PLUGIN_ROOT);
const isCodex   = !isCopilot && Boolean(process.env.PLUGIN_DATA);
const isQoder   = Boolean(process.env.QODER_SESSION_ID);

```

- **Claude Code**: Both `isCopilot` and `isCodex` remain `false`, triggering the native Claude path.
- **Codex**: `PLUGIN_DATA` is present, setting `isCodex = true`.
- **Copilot**: `COPILOT_PLUGIN_DATA` or a VS Code "agent-plugins" path sets `isCopilot = true`.

## The SessionStart Hook Entry Point

Claude Code, Codex, and Copilot all execute [`hooks/ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-activate.js) as a Node script (`#!/usr/bin/env node`) during the **SessionStart** lifecycle event. This entry point coordinates the activation sequence by calling helpers from [`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js).

```javascript
// hooks/ponytail-activate.js
const mode = getDefaultMode();  // e.g., "default", "ultra", or "off"

if (mode === 'off') {
  clearMode();
  writeHookOutput('SessionStart', 'off', isCodex || isCopilot ? '' : 'OK');
  process.exit(0);
}

setMode(mode);  // Persists activation flag
let output = getPonytailInstructions(mode);  // Builds prompt chunk
// ... status-line nudge logic ...
writeHookOutput('SessionStart', mode, output);  // Host-specific serialization

```

## Mode Persistence and Flag Files

The `setMode()` function writes a **mode flag** to `$CLAUDE_CONFIG_DIR/.ponytail-active` (defaulting to `~/.claude/.ponytail-active`). This file serves two purposes: it signals to the statusline badge that Ponytail is active, and it persists the chosen intensity level (`default`, `ultra`, etc.) across sessions.

If the `CLAUDE_CONFIG_DIR` environment variable is set, Ponytail respects that override. For Codex and Copilot, the path calculation falls back to `getClaudeDir()` from [`hooks/ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-config.js) when host-specific variables are unset.

## Host-Specific Output Formatting

The `writeHookOutput()` function in [`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js) abstracts the divergent payload expectations of each host. This ensures that **Claude Code** receives raw stdout, **Codex** receives a structured JSON blob with a system message, and **Copilot** receives `additionalContext`.

```javascript
// hooks/ponytail-runtime.js
function writeHookOutput(event, mode, context = '') {
  if (isCopilot) {
    // Copilot only consumes additionalContext on SessionStart
    process.stdout.write(JSON.stringify(
      event === 'SessionStart' && context ? { additionalContext: context } : {}));
    return;
  }
  
  if (isCodex) {
    // Codex expects systemMessage + hookSpecificOutput wrapper
    const output = { systemMessage: `PONYTAIL:${mode.toUpperCase()}` };
    if (context) {
      output.hookSpecificOutput = { 
        hookEventName: event, 
        additionalContext: context 
      };
    }
    process.stdout.write(JSON.stringify(output));
    return;
  }
  
  // Native Claude Code: raw stdout for SessionStart, JSON for SubagentStart
  if (event === 'SubagentStart') {
    process.stdout.write(JSON.stringify({ 
      hookSpecificOutput: { 
        hookEventName: event, 
        additionalContext: context 
      } 
    }));
    return;
  }
  
  process.stdout.write(context);  // Raw prompt text
}

```

## Optional Status-Line Nudging

When [`settings.json`](https://github.com/DietrichGebert/ponytail/blob/main/settings.json) lacks a `statusLine` entry, Ponytail creates a one-time flag file (`.ponytail-statusline-nudged`) and appends setup instructions to the output. This nudge is suppressed for Copilot because it does not read the status badge.

The nudge logic references helper scripts ([`ponytail-statusline.sh`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-statusline.sh) and `ponytail-statusline.ps1`) that users can wire into their configuration to display the active mode in their editor interface.

## Summary

- **Host detection** relies on environment variables (`COPILOT_PLUGIN_DATA`, `PLUGIN_DATA`) to branch logic for Claude Code, Codex, or Copilot.
- **[`hooks/ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-activate.js)** serves as the universal entry point executed on every SessionStart event.
- **`setMode()`** persists activation state to `~/.claude/.ponytail-active`, enabling status-line badges and mode memory.
- **`writeHookOutput()`** serializes payloads differently for each host: raw text for Claude Code, JSON with `systemMessage` for Codex, and `additionalContext` for Copilot.
- The same codebase runs on all three platforms, with [`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js) handling host-specific protocol differences.

## Frequently Asked Questions

### How does Ponytail distinguish between Claude Code and Microsoft Copilot?

Ponytail checks for the `COPILOT_PLUGIN_DATA` environment variable or a VS Code "agent-plugins" path in `CLAUDE_PLUGIN_ROOT` to identify Copilot. If neither is present but `PLUGIN_DATA` exists, it assumes Codex. When all are absent, it defaults to native Claude Code behavior.

### What file does Ponytail create to indicate it is active?

The plugin writes a flag file named `.ponytail-active` to `$CLAUDE_CONFIG_DIR` (typically `~/.claude/`). This file is created by the `setMode()` function in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js) and stores the current intensity mode.

### Why does Ponytail output different formats for each host?

Each AI assistant expects a different hook protocol. Claude Code reads raw stdout for SessionStart, legacy Codex requires a JSON wrapper with `systemMessage` and `hookSpecificOutput`, and Copilot only processes `additionalContext`. The `writeHookOutput()` function handles these variations automatically.

### Can I disable Ponytail after installation?

Yes. Setting the mode to `off` triggers an early exit in [`hooks/ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-activate.js). The script calls `clearMode()` to remove the flag file and emits an empty or "OK" payload depending on the host, effectively disabling the prompt injection for that session.