# How Ponytail Detects Copilot, Codex, and Qoder Host Environments

> Discover how Ponytail identifies Copilot, Codex, and Qoder host environments by analyzing unique session startup environment variables like COPILOT_PLUGIN_DATA.

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

---

**Ponytail detects its host environment by inspecting environment variables injected at session startup, using unique markers like `COPILOT_PLUGIN_DATA`, `PLUGIN_DATA`, and `QODER_SESSION_ID` to distinguish between VS Code Copilot, OpenAI Codex, and Qoder.**

Ponytail, an open-source runtime hook system for Claude-based workflows, must adapt its behavior depending on which AI coding assistant is hosting it. The detection logic in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js) identifies the host through a cascading check of well-known environment variables, each exclusive to a specific platform.

## Environment Variable Detection Strategy

Ponytail's host detection relies on three boolean flags evaluated at module load time. The order of checks matters: Copilot is tested first due to its dual detection paths, followed by Codex and Qoder as mutually exclusive fallbacks.

### VS Code Copilot Detection

Copilot presents two possible signatures. The primary marker is `COPILOT_PLUGIN_DATA`, set by the Copilot plugin directly. When this is absent—such as during VS Code execution—Ponytail falls back to `CLAUDE_PLUGIN_ROOT` and validates it through the `isVsCodeCopilotRoot` helper:

```js
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA) || isVsCodeCopilotRoot(process.env.CLAUDE_PLUGIN_ROOT);

```

The helper function, located at lines 13-17 in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js), verifies that the path contains `agent-plugins` and the case-insensitive substring `.vscode`, matching Copilot's characteristic plugin directory layout.

### OpenAI Codex Detection

Codex exposes a single unique marker: `PLUGIN_DATA`. Ponytail checks this only after confirming Copilot is not present, preventing false overlap:

```js
const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA);

```

This check appears at line 21 of [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js).

### Qoder Detection

Qoder supplies `QODER_SESSION_ID`, tested as the final branch when neither Copilot nor Codex conditions are met:

```js
const isQoder = !isCopilot && !isCodex && Boolean(process.env.QODER_SESSION_ID);

```

Found at line 22, this ensures Qoder is identified only when its exclusive session identifier is present.

## Why the Detection Order Matters

The cascading structure prevents misidentification where multiple environment variables might coexist.

- **Copilot's VS Code variant** lacks `COPILOT_PLUGIN_DATA` but reveals itself through directory structure, requiring the `CLAUDE_PLUGIN_ROOT` fallback
- **Codex and Qoder** use non-overlapping variables, but Ponytail explicitly excludes prior matches to guarantee unambiguous detection
- **Native Claude** is assumed when no host-specific markers exist, triggering fallback to `getClaudeDir()` for state directory resolution

## Practical Usage in Runtime Code

The detected flags drive all subsequent behavior. Below, Ponytail's runtime helpers adapt output formatting to the host environment:

```js
// Example: import the runtime helpers
const {
  isCopilot,
  isCodex,
  isQoder,
  writeHookOutput,
} = require('./hooks/ponytail-runtime');

// Show which host has been detected
if (isCopilot) {
  console.log('Running inside VS Code Copilot');
} else if (isCodex) {
  console.log('Running inside OpenAI Codex');
} else if (isQoder) {
  console.log('Running inside Qoder');
} else {
  console.log('Running under native Claude');
}

// Emit a hook-specific payload – Ponytail automatically picks the format
writeHookOutput('SessionStart', 'on', 'Welcome to Ponytail!');

```

The `writeHookOutput` function serializes payloads appropriately: **JSON** for Copilot, Codex, and Qoder; **plain text** for native Claude.

## Key Source Files

| File | Responsibility |
|------|--------------|
| [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js) | Core host detection, state-directory handling, and host-specific hook output formatting |
| [`hooks/ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-activate.js) | Consumes detection flags to conditionally emit hook output during activation |
| [`hooks/ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-config.js) | Provides `getClaudeDir()` and `getConfigDir()` for native Claude fallback scenarios |

## Summary

- **Copilot** detection uses `COPILOT_PLUGIN_DATA` or `CLAUDE_PLUGIN_ROOT` with path validation for `.vscode/agent-plugins/`
- **Codex** requires `PLUGIN_DATA` and is checked only after Copilot exclusion
- **Qoder** identifies via `QODER_SESSION_ID` as the final fallback
- All detection happens once at module load in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js), with resulting booleans driving runtime behavior
- Unsupported environments default to native Claude configuration

## Frequently Asked Questions

### What happens if multiple environment variables are set?

Ponytail's ordered checks prioritize Copilot first, then Codex, then Qoder. The first matching condition wins, preventing ambiguous states. This design assumes hosts are mutually exclusive in practice.

### Can the detection be overridden manually?

The source code in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js) does not expose manual override mechanisms. Detection is strictly environment-driven based on `process.env` inspection at require-time.

### Why does Copilot need two different detection methods?

The Copilot plugin sets `COPILOT_PLUGIN_DATA` directly, but VS Code's internal Claude integration uses `CLAUDE_PLUGIN_ROOT` instead. The `isVsCodeCopilotRoot` helper captures this alternate path pattern without breaking standard detection.

### How does Ponytail handle unknown or future hosts?

Any environment lacking `COPILOT_PLUGIN_DATA`, `CLAUDE_PLUGIN_ROOT`, `PLUGIN_DATA`, or `QODER_SESSION_ID` falls through to native Claude mode, using `getClaudeDir()` from [`hooks/ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-config.js) for state management.