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

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 and 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, 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.

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

// 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 when host-specific variables are unset.

Host-Specific Output Formatting

The writeHookOutput() function in 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.

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

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →