# How Ponytail Distinguishes Native Claude Code from VS Code Copilot

> Learn how the Ponytail plugin differentiates native Claude Code from VS Code Copilot by checking environment variables and parsing CLAUDE_PLUGIN_ROOT.

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

---

**Ponytail checks for the `COPILOT_PLUGIN_DATA` environment variable and parses `CLAUDE_PLUGIN_ROOT` to determine if it is running inside VS Code Copilot or native Claude Code.**

The DietrichGebert/ponytail repository provides a plugin that must adapt its behavior depending on whether it executes within the native Claude Code CLI or the VS Code Copilot extension. This distinction is critical because each host expects different state directories, hook output formats, and session management protocols. The detection logic resides primarily in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js) and relies on environment variables injected by the host during initialization.

## Environment Variable Detection

Ponytail employs two primary environment variables to distinguish native Claude Code from VS Code Copilot. The plugin inspects these variables at runtime to set an internal `isCopilot` flag that governs downstream behavior.

### The COPILOT_PLUGIN_DATA Flag

VS Code Copilot injects a unique environment variable named **`COPILOT_PLUGIN_DATA`**, which points to the Copilot plugin’s data directory. If this variable is defined, Ponytail immediately knows it is operating under the Copilot extension. This is the most direct detection mechanism available.

### Parsing CLAUDE_PLUGIN_ROOT

Both native Claude Code and VS Code Copilot set the **`CLAUDE_PLUGIN_ROOT`** environment variable, but the path structure differs between hosts:

- **VS Code Copilot**: The path contains the `.vscode/agent-plugins/` folder segment
- **Native Claude Code**: The path points to a standard Claude plugin location (e.g., `~/.claude/plugins/…`)

## Detection Algorithm Implementation

The core detection logic uses a helper function `isVsCodeCopilotRoot()` to analyze the `CLAUDE_PLUGIN_ROOT` path. This function checks whether the path includes the segment `agent-plugins` **and** contains the string `.vscode` (case-insensitive).

```javascript
// hooks/ponytail-runtime.js
function isVsCodeCopilotRoot(pluginRoot) {
  if (!pluginRoot) return false;
  return pluginRoot.split(/[\\/]+/).includes('agent-plugins') &&
    pluginRoot.toLowerCase().includes('.vscode');
}

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

```

If either `COPILOT_PLUGIN_DATA` exists or `isVsCodeCopilotRoot()` returns true, the runtime sets `isCopilot = true`. Otherwise, the plugin assumes it is running in native Claude Code (or potentially other hosts like Codex or Qoder, which are detected via additional environment variables).

### State Directory Resolution

Based on the detection result, Ponytail selects appropriate directories for storing state files:

```javascript
// hooks/ponytail-runtime.js (excerpt)
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA) ||
  isVsCodeCopilotRoot(process.env.CLAUDE_PLUGIN_ROOT);
const isCodex   = !isCopilot && Boolean(process.env.PLUGIN_DATA);
const isQoder   = !isCopilot && !isCodex && Boolean(process.env.QODER_SESSION_ID);

let stateDir = getClaudeDir();               // default = native Claude
if (isCodex)   stateDir = process.env.PLUGIN_DATA;
if (isCopilot) stateDir = process.env.COPILOT_PLUGIN_DATA || getClaudeDir();
if (isQoder)   stateDir = path.join(os.homedir(), '.qoder');

```

## Host-Specific Runtime Behaviors

Once the plugin distinguishes native Claude Code from VS Code Copilot, it adapts three key behaviors:

### State Directory for Session Flags

- **Copilot**: Uses `process.env.COPILOT_PLUGIN_DATA` as the state directory for the `.ponytail-active` flag, falling back to the Claude directory only if undefined
- **Native Claude Code**: Uses the standard Claude configuration directory returned by `getClaudeDir()`

### Hook Output Format

The `writeHookOutput()` function in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js) emits different JSON structures depending on the host:

```javascript
function writeHookOutput(event, mode, context = '') {
  if (isCopilot) {
    // Copilot only cares about `additionalContext` on SessionStart
    process.stdout.write(JSON.stringify(
      event === 'SessionStart' && context ? { additionalContext: context } : {}));
    return;
  }
  // Native Claude Code emits raw stdout or full JSON with hookSpecificOutput
}

```

- **Copilot**: Emits only `additionalContext` for the `SessionStart` event, providing Copilot with contextual hints
- **Native Claude Code**: Emits raw stdout or full JSON responses including `hookSpecificOutput` for `SubagentStart` events

### Status-Line Nudging

- **Copilot**: Status-line nudging is **disabled** because Copilot never reads the nudge output
- **Native Claude Code**: Status-line nudging is **enabled** to provide real-time feedback to Claude Code users

## Summary

- Ponytail distinguishes native Claude Code from VS Code Copilot by checking `process.env.COPILOT_PLUGIN_DATA` and parsing `process.env.CLAUDE_PLUGIN_ROOT` via the `isVsCodeCopilotRoot()` helper.
- The `isVsCodeCopilotRoot()` function specifically looks for the `agent-plugins` path segment combined with `.vscode` to identify Copilot environments.
- Detection affects critical behaviors including state directory selection, hook output JSON structure, and UI nudging capabilities.
- All detection logic is centralized in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js) with configuration helpers available in [`hooks/ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-config.js).

## Frequently Asked Questions

### How reliable is environment variable detection for distinguishing Claude Code hosts?

Environment variable detection is highly reliable because both native Claude Code and VS Code Copilot explicitly set distinct variables (`CLAUDE_PLUGIN_ROOT` and `COPILOT_PLUGIN_DATA`) during plugin initialization. These variables are injected by the host process before Ponytail executes, making them trustworthy indicators of the runtime environment.

### Can Ponytail detect other AI coding assistants besides Claude Code and Copilot?

Yes, the plugin includes detection for additional hosts. After ruling out Copilot, Ponytail checks for `process.env.PLUGIN_DATA` to identify **Codex** environments, and `process.env.QODER_SESSION_ID` to identify **Qoder** sessions. Each host receives customized state directory assignments and output formatting appropriate to its protocol.

### Why does Copilot require different hook output than native Claude Code?

VS Code Copilot expects a specific JSON structure containing only `additionalContext` during `SessionStart` events to provide session-wide context hints. Native Claude Code, conversely, supports richer hook outputs including `hookSpecificOutput` and raw stdout streams for subagent communication. Ponytail branches its `writeHookOutput()` function to satisfy these distinct host expectations.

### What happens if neither COPILOT_PLUGIN_DATA nor CLAUDE_PLUGIN_ROOT is set?

If neither environment variable is defined, `isVsCodeCopilotRoot()` returns false due to the null check, and `isCopilot` evaluates to false. Consequently, Ponytail defaults to native Claude Code behavior, using `getClaudeDir()` for state storage and enabling full hook output capabilities. This fallback ensures the plugin remains functional in standard Claude Code installations even if specific variables are missing.