How Ponytail Detects VS Code Copilot: Environment Variable Analysis

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, specifically between lines 13 and 21.

The isVsCodeCopilotRoot function implements the path validation:

// 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:

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

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

Example 2: VS Code Copilot Session

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

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

To inspect the detection result programmatically:

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

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

  • hooks/ponytail-runtime.js — Contains the isVsCodeCopilotRoot function and isCopilot state initialization (lines 13-21).
  • 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 — 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 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 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.

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 →