How Ponytail Activates on Different Platforms: VS Code Copilot, Codex, and Native Claude Support

Ponytail activates via a SessionStart hook that detects the runtime environment, writes a platform-specific flag file, and emits tailored instruction sets for VS Code Copilot, Claude Codex, Qoder, or native Claude.

The DietrichGebert/ponytail repository implements a unified activation system that automatically adapts to whichever Claude-based client is running. When a session begins, the hooks/ponytail-activate.js hook executes a three-phase pipeline: platform detection, state persistence, and context emission. This ensures consistent behavior across macOS, Linux, and Windows while respecting each client's unique integration constraints.

Platform Detection in ponytail-runtime.js

All platform identification logic resides in hooks/ponytail-runtime.js. The hook distinguishes between four execution environments by inspecting specific environment variables and path patterns.

VS Code Copilot Detection

The hook identifies VS Code Copilot by checking if process.env.COPILOT_PLUGIN_DATA is set or if CLAUDE_PLUGIN_ROOT contains both agent-plugins and .vscode substrings. This logic is encapsulated in the isCopilot boolean flag defined at lines 19-21 of hooks/ponytail-runtime.js.

Claude Codex Detection

Codex sessions are detected when process.env.PLUGIN_DATA exists and the Copilot condition is false. This isCodex check at lines 21-22 ensures the hook does not misclassify Codex instances running within plugin-capable environments.

Qoder Detection

The isQoder flag (lines 22-23) triggers when process.env.QODER_SESSION_ID is present and neither Copilot nor Codex conditions are met. This provides dedicated support for the Qoder IDE integration.

Native Claude Fallback

When none of the above environment variables are present, the hook defaults to native Claude mode. This fallback branch handles standard Claude Desktop or CLI installations without plugin-specific modifications.

Writing the Activation Flag and State Management

Once the platform is identified, setMode(mode) in hooks/ponytail-activate.js creates a .ponytail-active flag file in a platform-specific state directory.

The state directory resolution follows this precedence (as implemented in hooks/ponytail-runtime.js):

  • Default: ~/.claude (via getClaudeDir() in hooks/ponytail-config.js)
  • Codex: process.env.PLUGIN_DATA
  • Copilot: process.env.COPILOT_PLUGIN_DATA or falls back to ~/.claude
  • Qoder: ~/.qoder

The setMode() function (lines 33-36) recursively creates the target directory if missing and writes the mode value (full, lite, ultra, or off) to .ponytail-active. If the resolved mode is off, the hook calls clearMode() (lines 26-31) to delete the flag file and exit early without emitting instructions.

Platform-Specific Output Routing

The writeHookOutput function routes activation instructions differently for each client to maximize compatibility:

  • Copilot: Emits a JSON object containing only additionalContext for SessionStart events (lines 51-58)
  • Codex: Sends a system message formatted as PONYTAIL:<MODE> followed by a hookSpecificOutput JSON payload (lines 59-67)
  • Qoder: Uses the same JSON payload as Codex but omits the system message prefix (lines 69-78)
  • Native Claude: Outputs raw text for SessionStart, but uses the JSON wrapper for SubagentStart to prevent context loss (lines 82-89)

This differentiation ensures that VS Code Copilot receives properly scoped context while Codex gets the explicit system message required for its architecture.

Status-Line Setup Nudges

When activation occurs, the hook checks for the presence of a statusLine configuration in Claude settings. If absent and the platform is not Copilot, Ponytail emits a one-time setup nudge pointing to platform-specific helper scripts.

The script selection logic (lines 62-69 in hooks/ponytail-activate.js) uses process.platform === 'win32' to determine the appropriate helper:

The hook embeds the selected script path in a JSON snippet suitable for pasting into settings.json. If the installation path contains unsafe shell characters (detected via isShellSafe()), it falls back to plain text instructions instead of embedded commands (lines 70-85). To avoid repeated notifications, the hook creates a .ponytail-statusline-nudged flag file after the first display (lines 56-60).

Default Mode Resolution

Before writing activation state, getDefaultMode() in hooks/ponytail-config.js (lines 76-84) resolves the initial operating mode through the following precedence:

  1. PONYTAIL_DEFAULT_MODE environment variable
  2. config.json in the XDG or Windows configuration directory
  3. Hardcoded fallback to 'full'

The function validates that only runtime-level modes (off, lite, full, ultra) are permitted; the session-only review mode is explicitly rejected to prevent invalid default configurations.

Summary

  • Platform detection relies on environment variables (COPILOT_PLUGIN_DATA, PLUGIN_DATA, QODER_SESSION_ID) defined in hooks/ponytail-runtime.js
  • State persistence uses .ponytail-active flag files written to platform-specific directories via setMode() and clearMode()
  • Output routing adapts instruction format per client: JSON context for Copilot, system messages for Codex, raw text for native Claude
  • Status-line helpers are selected by process.platform checks, providing .ps1 scripts for Windows and .sh scripts for Unix systems
  • Mode resolution follows a strict hierarchy from environment variables to config files to the 'full' default

Frequently Asked Questions

How can I manually set Ponytail to operate in lite mode across all sessions?

Set the PONYTAIL_DEFAULT_MODE environment variable to 'lite' before starting your Claude client. Alternatively, modify the config.json file in your platform's configuration directory (managed by getClaudeDir() in hooks/ponytail-config.js) to include "defaultMode": "lite".

Why does Ponytail use different output formats for VS Code Copilot versus Claude Codex?

VS Code Copilot requires wrapped JSON context via additionalContext to integrate with its chat interface, while Claude Codex expects a system message prefix (PONYTAIL:<MODE>) to properly register the plugin state. The writeHookOutput function in hooks/ponytail-runtime.js handles these distinctions automatically based on the isCopilot and isCodex flags.

Where does Ponytail store its activation flag on Windows versus Linux?

On Windows, the flag writes to the directory specified by COPILOT_PLUGIN_DATA (for Copilot) or %USERPROFILE%\.claude (for native Claude). On Linux and macOS, it uses $PLUGIN_DATA (Codex), ~/.qoder (Qoder), or ~/.claude (native). All paths are computed dynamically in hooks/ponytail-runtime.js before calling setMode().

How do I prevent Ponytail from showing the status-line setup nudge repeatedly?

The hook automatically creates a .ponytail-statusline-nudged file in your state directory after displaying the notification once. If you accidentally dismiss it or need to restore it, delete this hidden flag file and restart your Claude session to trigger hooks/ponytail-activate.js again.

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 →