fix: detect VS Code Copilot via CLAUDE_PLUGIN_ROOT fallback (#528) (#579)

VS Code Copilot never sets COPILOT_PLUGIN_DATA, only CLAUDE_PLUGIN_ROOT
(pointed at an .vscode/agent-plugins/... install path), so isCopilot was
false and ponytail assumed native Claude Code — emitting the statusline
setup nudge, which VS Code Copilot doesn't read. Also guard stateDir:
it was built from the same unset COPILOT_PLUGIN_DATA whenever isCopilot
resolved via this new fallback, so fall back to getClaudeDir() there too.
This commit is contained in:
krish rathi
2026-08-08 03:07:09 +05:30
committed by GitHub
parent cc37a5d581
commit a2712bc830
2 changed files with 47 additions and 2 deletions
+17 -2
View File
@@ -4,13 +4,28 @@ const os = require('os');
const { getClaudeDir, getConfigDir } = require('./ponytail-config');
const STATE_FILE = '.ponytail-active';
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA);
// ponytail: VS Code Copilot never sets COPILOT_PLUGIN_DATA — it only injects
// CLAUDE_PLUGIN_ROOT, pointed at an install path under .vscode/agent-plugins/
// (#528). Without this fallback isCopilot was false, so ponytail assumed
// native Claude Code and emitted the statusline nudge, which VS Code Copilot
// doesn't read.
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);
const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA);
const isQoder = !isCopilot && !isCodex && Boolean(process.env.QODER_SESSION_ID);
let stateDir = getClaudeDir();
if (isCodex) stateDir = process.env.PLUGIN_DATA;
if (isCopilot) stateDir = process.env.COPILOT_PLUGIN_DATA;
// COPILOT_PLUGIN_DATA is unset under VS Code Copilot, so fall back to
// getClaudeDir() rather than building a path from undefined.
if (isCopilot) stateDir = process.env.COPILOT_PLUGIN_DATA || getClaudeDir();
if (isQoder) stateDir = path.join(os.homedir(), '.qoder');
const statePath = path.join(stateDir, STATE_FILE);
+30
View File
@@ -198,6 +198,36 @@ assert.equal(
output = JSON.parse(result.stdout);
assert.match(output.additionalContext, /PONYTAIL MODE ACTIVE — level: full/);
// VS Code Copilot never sets COPILOT_PLUGIN_DATA — it only injects
// CLAUDE_PLUGIN_ROOT pointed at an agent-plugins/.../.vscode install path
// (#528). Without a fallback, isCopilot was false, so ponytail assumed
// native Claude Code and emitted the statusline nudge — noise, since VS
// Code Copilot doesn't read Claude's statusLine setting.
const vscodeHome = path.join(temp, 'vscode-copilot-home');
const vscodePluginRoot = path.join(
vscodeHome, '.vscode', 'agent-plugins', 'github.com', 'DietrichGebert', 'ponytail', 'hooks',
);
fs.mkdirSync(vscodeHome, { recursive: true });
result = run('ponytail-activate.js', {
HOME: vscodeHome,
USERPROFILE: vscodeHome,
CLAUDE_PLUGIN_ROOT: vscodePluginRoot,
PONYTAIL_DEFAULT_MODE: 'full',
});
assert.equal(result.status, 0, result.stderr);
assert.ok(
!result.stdout.includes('STATUSLINE SETUP NEEDED'),
'VS Code Copilot (detected via CLAUDE_PLUGIN_ROOT) must not get the Claude-only statusline nudge',
);
// isCopilot must still resolve a state dir even though COPILOT_PLUGIN_DATA
// is unset under VS Code — falling back to ~/.claude, not crashing on an
// undefined path.
assert.equal(
fs.readFileSync(path.join(vscodeHome, '.claude', '.ponytail-active'), 'utf8'),
'full',
'VS Code Copilot must persist mode state under getClaudeDir(), not a path built from the unset COPILOT_PLUGIN_DATA',
);
result = run(
'ponytail-mode-tracker.js',
{