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

> Discover how Ponytail detects its hosting AI agent including Claude Code, Copilot, Codex, and Qoder. Learn about its environment variable inspection process.

- Repository: [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail)
- Tags: internals
- Published: 2026-09-09

---

**Ponytail determines which AI agent hosts it by inspecting a prioritized set of environment variables in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/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`](https://github.com/DietrichGebert/ponytail/blob/main/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:

```javascript
// 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
}

```

```javascript
// 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

```

```javascript
// 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`](https://github.com/DietrichGebert/ponytail/blob/main/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`](https://github.com/DietrichGebert/ponytail/blob/main/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.