# How Ponytail Detects VS Code Copilot: Environment Variable Analysis

> Discover how Ponytail detects VS Code Copilot by analyzing environment variables like CLAUDE_PLUGIN_ROOT and COPILOT_PLUGIN_DATA. Understand Copilot detection methods.

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

---

**Ponytail detects VS Code Copilot by inspecting the `CLAUDE_PLUGIN_ROOT` environment variable for a path containing both `.vscode` and `agent-plugins`, while also checking for the presence of `COPILOT_PLUGIN_DATA` to identify stand-alone Copilot sessions.**

Ponytail is an open-source plugin that adapts its runtime behavior based on whether it executes inside VS Code Copilot or a standard Claude environment. The detection mechanism relies on specific environment variables set by the host application, implemented in the runtime initialization hooks.

## Environment Variable Detection Strategy

Ponytail uses a dual-signal approach to determine the execution context. The system checks for two distinct environment variables that indicate different Copilot deployment scenarios.

### The Two Copilot Signals

The detection logic evaluates two environment variables:

- **`COPILOT_PLUGIN_DATA`** — Present only when running as a **stand-alone Copilot** plugin. When defined, Ponytail immediately treats the session as Copilot without further path inspection.
- **`CLAUDE_PLUGIN_ROOT`** — Set exclusively by **VS Code Copilot** to point at the extension's installation directory under `.vscode/agent-plugins/`.

If either variable indicates a Copilot environment, Ponytail sets `isCopilot = true`.

### Path Heuristic for VS Code Installation

For VS Code Copilot detection, Ponytail runs a path analysis on `CLAUDE_PLUGIN_ROOT` via the `isVsCodeCopilotRoot` function. The heuristic validates two conditions:

1. The path contains the segment `agent-plugins`
2. The path includes `.vscode` (case-insensitive substring match)

Only when both conditions satisfy does the function return `true`, confirming a VS Code Copilot installation.

## Implementation in ponytail-runtime.js

The core detection logic resides in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js), specifically between lines 13 and 21.

The `isVsCodeCopilotRoot` function implements the path validation:

```javascript
// 1️⃣ Detect VS Code Copilot via its install path
function isVsCodeCopilotRoot(pluginRoot) {
  if (!pluginRoot) return false;
  return pluginRoot.split(/[\\/]+/).includes('agent-plugins') &&
         pluginRoot.toLowerCase().includes('.vscode');
}

```

The function splits the path by directory separators and checks for the `agent-plugins` directory, while simultaneously converting the entire path to lowercase to check for `.vscode`.

The `isCopilot` flag combines both detection methods:

```javascript
// 2️⃣ Combine the two possible signals
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA) ||
                 isVsCodeCopilotRoot(process.env.CLAUDE_PLUGIN_ROOT);

```

As implemented in DietrichGebert/ponytail, this logic runs during module initialization to set the runtime context before any output formatting occurs.

## Behavioral Changes When Copilot is Detected

When `isCopilot` evaluates to `true`, Ponytail modifies two critical behaviors:

**State Storage Location** — Ponytail stores session state under the Copilot-specific directory defined in `process.env.COPILOT_PLUGIN_DATA`. If this variable is undefined (as with VS Code Copilot), the system falls back to the regular Claude directory.

**Output Format** — Instead of emitting Claude's native `systemMessage` format, Ponytail outputs JSON using the `additionalContext` structure expected by the Copilot host interface.

## Practical Detection Examples

You can simulate different environments by setting the appropriate environment variables before requiring the runtime module.

### Example 1: Stand-alone Copilot Plugin

```javascript
process.env.COPILOT_PLUGIN_DATA = '/tmp/copilot-data';
require('./hooks/ponytail-runtime'); // isCopilot === true

```

### Example 2: VS Code Copilot Session

```javascript
delete process.env.COPILOT_PLUGIN_DATA;                 // not set by VS Code
process.env.CLAUDE_PLUGIN_ROOT = '/home/me/.vscode/agent-plugins/github.com/DietrichGebert/ponytail/hooks';
require('./hooks/ponytail-runtime'); // isCopilot === true via path heuristic

```

### Example 3: Standard Claude Environment

```javascript
delete process.env.COPILOT_PLUGIN_DATA;
delete process.env.CLAUDE_PLUGIN_ROOT;
require('./hooks/ponytail-runtime'); // isCopilot === false

```

To inspect the detection result programmatically:

```javascript
const rt = require('./hooks/ponytail-runtime');
console.log(rt.isCopilot); // true for examples 1 & 2, false for 3

```

## Related Files in the Detection Flow

The complete detection and adaptation flow spans three key files in the DietrichGebert/ponytail repository:

- **[`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js)** — Contains the `isVsCodeCopilotRoot` function and `isCopilot` state initialization (lines 13-21).
- **[`hooks/ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-activate.js)** — Entry point that consumes the `isCopilot` flag to determine whether to display the Claude status-line nudge.
- **[`tests/hooks.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/hooks.test.js)** — Test suite validating VS Code Copilot detection logic and fallback behavior for missing environment variables.

## Summary

- Ponytail detects VS Code Copilot by analyzing `CLAUDE_PLUGIN_ROOT` for paths containing both `.vscode` and `agent-plugins`.
- The `isVsCodeCopilotRoot` function in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js) performs case-insensitive path inspection to identify VS Code installations.
- Stand-alone Copilot detection relies on the `COPILOT_PLUGIN_DATA` environment variable.
- When detected, Ponytail switches to Copilot-compatible state storage and emits `additionalContext` JSON instead of standard Claude messages.
- The detection mechanism allows the same codebase to run seamlessly across VS Code Copilot, stand-alone Copilot, and standard Claude environments.

## Frequently Asked Questions

### How does Ponytail distinguish between VS Code Copilot and stand-alone Copilot?

Ponytail distinguishes between the two by checking different environment variables. Stand-alone Copilot sets `COPILOT_PLUGIN_DATA`, while VS Code Copilot sets `CLAUDE_PLUGIN_ROOT`. The `isVsCodeCopilotRoot` function specifically analyzes the VS Code path for `.vscode` and `agent-plugins` segments, allowing Ponytail to differentiate the host environment even when `COPILOT_PLUGIN_DATA` is absent.

### What happens if neither environment variable is set?

If both `COPILOT_PLUGIN_DATA` and `CLAUDE_PLUGIN_ROOT` are undefined, the `isCopilot` flag evaluates to `false`. In this scenario, Ponytail operates in standard Claude mode, using the default Claude directory for state storage and emitting `systemMessage` formatted output rather than Copilot's `additionalContext` JSON.

### Can the detection logic be fooled by manually setting environment variables?

Yes, the detection relies entirely on environment variable inspection, so manually setting `CLAUDE_PLUGIN_ROOT` to a path containing `.vscode` and `agent-plugins` will cause Ponytail to treat the session as VS Code Copilot. However, this is the intended behavior for testing and development purposes, as demonstrated in the [`tests/hooks.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/hooks.test.js) file.

### Why does Ponytail check for `.vscode` case-insensitively?

The `isVsCodeCopilotRoot` function converts the plugin root path to lowercase using `toLowerCase()` before checking for `.vscode` because file system case sensitivity varies across operating systems. This ensures reliable detection on case-insensitive file systems like macOS and Windows, while maintaining compatibility with case-sensitive Linux systems.