How Ponytail Detects the Hosting Agent (Claude Code, Copilot, Codex, Qoder)

Ponytail determines which AI agent hosts it by inspecting a prioritized set of environment variables in hooks/ponytail-runtime.js, distinguishing VS Code Copilot, Claude Codex, Qoder, and native Claude Code through cascading conditional checks.

The open-source ponytail repository by DietrichGebert implements lightweight, portable detection logic that adapts its behavior to whichever coding agent launched it. Instead of relying on complex feature detection, the system performs a simple hierarchy of environment variable lookups to identify the host and configure its state directory accordingly.

Environment Variable Detection Logic

The detection mechanism resides in hooks/ponytail-runtime.js and evaluates four specific environment variables in a fixed order. This cascading approach ensures unambiguous host identification without false positives.

VS Code Copilot Detection

Ponytail identifies VS Code Copilot by checking if process.env.COPILOT_PLUGIN_DATA is defined, or if the CLAUDE_PLUGIN_ROOT path contains the pattern agent-plugins inside a .vscode directory. The code at lines 19-20 implements this dual-check logic to catch both standard and edge-case Copilot installations.

Claude Codex Detection

When Copilot is not detected, the system checks for Claude Codex by verifying that process.env.PLUGIN_DATA is defined (line 21). This variable is unique to the Codex plugin environment, making the distinction between Codex and native Claude Code straightforward.

Qoder Detection

If neither Copilot nor Codex is present, Ponytail tests for Qoder by looking for process.env.QODER_SESSION_ID (line 22). This session identifier is exclusively set by the Qoder agent, providing a reliable third-tier detection mechanism.

Native Claude Code Fallback

When none of the above environment variables are present, Ponytail defaults to native Claude Code mode. This fallback requires no additional configuration, as the absence of plugin-specific variables indicates a direct Claude CLI invocation.

State Directory Configuration by Host

After detecting the host, Ponytail normalizes the state directory path where it stores its runtime flag (.ponytail-active). The selection logic at lines 24-29 maps each host to its appropriate storage location:

  • Native Claude: Uses getClaudeDir(), defaulting to ~/.claude or respecting process.env.CLAUDE_CONFIG_DIR
  • Claude Codex: Uses process.env.PLUGIN_DATA directly as the state directory
  • VS Code Copilot: Prefers process.env.COPILOT_PLUGIN_DATA, falling back to getClaudeDir() if unavailable
  • Qoder: Constructs a dedicated path at ~/.qoder using path.join(os.homedir(), '.qoder')

These boolean flags (isCopilot, isCodex, isQoder) are exported from the runtime module and consumed throughout the codebase to tailor behavior. The writeHookOutput function serializes responses differently for each host—JSON format for Copilot, Codex, and Qoder, versus raw stdout for native Claude—while setMode, clearMode, and readMode operate on the host-specific stateDir.

Practical Implementation Examples

The exported detection flags enable conditional logic in your hooks. Here is how to implement host-aware functionality:

// Adapt output format automatically to the detected host
const { isCopilot, isCodex, isQoder, writeHookOutput } = require('./hooks/ponytail-runtime');

function handleEvent(event, mode, context) {
  // Business logic here...
  writeHookOutput(event, mode, context); // Automatically selects JSON or raw format
}
// Retrieve the current mode regardless of hosting environment
const { readMode } = require('./hooks/ponytail-runtime');

const currentMode = readMode(); // Returns 'full', 'lite', etc., or null if inactive
// Implement custom host-specific behavior
const { isCopilot, isCodex, isQoder } = require('./hooks/ponytail-runtime');

if (isCopilot) {
  console.log('Running inside VS Code Copilot');
  // Apply VS Code-specific workarounds
} else if (isCodex) {
  console.log('Running inside Claude Codex');
} else if (isQoder) {
  console.log('Running inside Qoder');
} else {
  console.log('Running inside native Claude Code');
}

The helper function isVsCodeCopilotRoot specifically recognizes the VS Code path pattern that Copilot injects (…/.vscode/agent-plugins/…), providing additional validation for edge cases where environment variables might be ambiguous.

Summary

  • Pure environment-variable detection: Ponytail uses COPILOT_PLUGIN_DATA, PLUGIN_DATA, and QODER_SESSION_ID to distinguish hosts in hooks/ponytail-runtime.js
  • Cascading priority: Checks Copilot first, then Codex, then Qoder, defaulting to native Claude Code
  • Dynamic state directories: Maps each host to its correct configuration path, from ~/.claude to ~/.qoder
  • Behavioral adaptation: Exports isCopilot, isCodex, and isQoder booleans to customize output formatting and file operations
  • Zero-dependency approach: Requires no external libraries or complex heuristics, making the detection robust across platforms

Frequently Asked Questions

How does Ponytail distinguish between VS Code Copilot and Claude Codex?

Ponytail checks for process.env.COPILOT_PLUGIN_DATA or a specific .vscode/agent-plugins path pattern first (lines 19-20). Only if Copilot is not detected does it look for process.env.PLUGIN_DATA to identify Claude Codex (line 21). This ordered evaluation prevents Codex from being misidentified when running inside VS Code.

What happens if no environment variables are set?

When COPILOT_PLUGIN_DATA, PLUGIN_DATA, and QODER_SESSION_ID are all undefined, Ponytail assumes it is running under native Claude Code. It defaults to using getClaudeDir() (typically ~/.claude) for state management and outputs raw stdout instead of JSON-formatted responses.

Can I override the detected host manually?

According to the source code in hooks/ponytail-runtime.js, the detection is automatic based on environment variables at startup. There is no exposed configuration flag to force a specific host mode; however, you could manipulate the relevant environment variables (PLUGIN_DATA, QODER_SESSION_ID, etc.) before requiring the module to simulate a different host environment.

Where does Qoder store its Ponytail state files?

When isQoder evaluates to true (detected via process.env.QODER_SESSION_ID at line 22), Ponytail constructs the state directory by joining the user's home directory with .qoder using path.join(os.homedir(), '.qoder') at line 29. This isolated path prevents conflicts with Claude Code or Copilot configurations.

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 →