fix(#812): honor COPILOT_HOME in Copilot global config-dir resolution (#814)

* fix(#812): honor COPILOT_HOME in Copilot global config-dir resolution

getGlobalConfigDir('copilot') resolved the global config directory using
only --config-dir > COPILOT_CONFIG_DIR > ~/.copilot, ignoring the
COPILOT_HOME env var. Per GitHub's Copilot CLI docs, COPILOT_HOME
overrides the default ~/.copilot location (and user-level hooks are read
from $COPILOT_HOME/hooks/), so a global --copilot install wrote all
artifacts (skills, agents, copilot-instructions.md, the gsd-session.json
hook) to ~/.copilot even when the user relocated their Copilot home,
making them undiscoverable by Copilot CLI.

Mirror the codex/CODEX_HOME branch: precedence is now
--config-dir > COPILOT_CONFIG_DIR > COPILOT_HOME > ~/.copilot. Uninstall
uses the same resolver, so it stays symmetric.

Also: document COPILOT_HOME in the installer --help notes, the
USER-GUIDE env-var table, and the installer-migrations Copilot row; and
clear COPILOT_HOME in the two default-path test suites so they stay
hermetic now that the resolver honors it.

Closes #812

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#812): add changeset for PR #814

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-07 15:27:46 -04:00
committed by GitHub
parent eefef2ec19
commit 3025a6846e
8 changed files with 49 additions and 6 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 814
---
Honor the `COPILOT_HOME` environment variable when resolving the GitHub Copilot global config directory. Previously a global `--copilot` install ignored `COPILOT_HOME` and wrote all artifacts (skills, agents, `copilot-instructions.md`, the session hook) to `~/.copilot` even when the user had relocated their Copilot home, making them undiscoverable by Copilot CLI. Resolution now follows `--config-dir` > `COPILOT_CONFIG_DIR` > `COPILOT_HOME` > `~/.copilot`, mirroring the existing `CODEX_HOME` handling. Uninstall uses the same resolver and stays symmetric. (#812)

File diff suppressed because one or more lines are too long

View File

@@ -749,7 +749,7 @@ WINDSURF_CONFIG_DIR=~/.codeium/windsurf-next npx @opengsd/gsd-core@latest --wind
| Gemini CLI | `~/.gemini` | `GEMINI_CONFIG_DIR` |
| OpenCode | `XDG_CONFIG_HOME/opencode` | `OPENCODE_CONFIG_DIR` |
| Codex | (per Codex CLI) | `--config-dir` flag |
| Copilot | `~/.copilot` | `COPILOT_CONFIG_DIR` |
| Copilot | `~/.copilot` | `COPILOT_CONFIG_DIR` (or `COPILOT_HOME`) |
| Cursor | `~/.cursor` | `CURSOR_CONFIG_DIR` |
| Windsurf | `~/.codeium/windsurf` | `WINDSURF_CONFIG_DIR` |
| Antigravity | auto-detected | `ANTIGRAVITY_CONFIG_DIR` |

View File

@@ -368,7 +368,7 @@ for the new shape before changing migration behavior.
| Kilo | OpenCode-style flat markdown commands in `command/gsd-*.md`; agents in `agents/gsd-*.md`; config updates in `kilo.json` or `kilo.jsonc` | Global `KILO_CONFIG_DIR`, `dirname(KILO_CONFIG)`, `XDG_CONFIG_HOME/kilo`, or `~/.config/kilo`; local `./.kilo` | GSD owns generated command/agent files and GSD entries in structured config only | [Custom subagents](https://docs.kilo.ai/docs/customize/custom-subagents); docs not versioned, checked 2026-05-11 |
| Gemini CLI | TOML slash commands in `commands/gsd/*.toml`; agents in `agents/gsd-*.md`; `settings.json` feature flag, hooks, and statusline | Global `GEMINI_CONFIG_DIR` or `~/.gemini`; local `./.gemini` | GSD owns generated commands/agents/hooks and only GSD settings entries; local command copy may be skipped when global GSD commands already exist | [Custom commands](https://google-gemini.github.io/gemini-cli/docs/cli/custom-commands.html), [configuration](https://google-gemini.github.io/gemini-cli/docs/cli/configuration.html); docs checked 2026-05-11 |
| Codex | Skills in `skills/gsd-*/SKILL.md`; agents as source markdown plus per-agent TOML in `agents/`; `[agents.gsd-*]` and hooks in `config.toml` | Global `CODEX_HOME` or `~/.codex`; local `./.codex` | GSD owns generated skills, generated agent TOML, `agents.gsd-*` config sections, `[features].hooks` when added by GSD (canonical; legacy alias `codex_hooks` is recognized and migrated forward, #3566), and GSD hook entries | [Codex config schema](https://developers.openai.com/codex/config-schema.json), [Codex developer docs](https://developers.openai.com/codex/); docs not versioned, checked 2026-05-15; installer compatibility sentinel: Codex 0.130.0 features.hooks key (legacy `codex_hooks` recognized) |
| GitHub Copilot | Skills in `skills/gsd-*/SKILL.md`; agents as `.agent.md`; repository instructions in `copilot-instructions.md` | Global `COPILOT_CONFIG_DIR` or `~/.copilot`; local `./.github` | GSD owns generated skill/agent files and GSD-authored instruction files; no hook/statusline ownership | [Repository custom instructions](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions), [Copilot CLI custom instructions](https://docs.github.com/en/copilot/how-tos/copilot-cli/add-custom-instructions); GitHub Docs product docs, checked 2026-05-11 |
| GitHub Copilot | Skills in `skills/gsd-*/SKILL.md`; agents as `.agent.md`; repository instructions in `copilot-instructions.md` | Global `COPILOT_CONFIG_DIR`, `COPILOT_HOME`, or `~/.copilot`; local `./.github` | GSD owns generated skill/agent files and GSD-authored instruction files; no hook/statusline ownership | [Repository custom instructions](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions), [Copilot CLI custom instructions](https://docs.github.com/en/copilot/how-tos/copilot-cli/add-custom-instructions); GitHub Docs product docs, checked 2026-05-11 |
| Antigravity | Skills in `skills/gsd-*/SKILL.md`; agents in `agents/`; Gemini-style `settings.json` hooks when installed by GSD | Global `ANTIGRAVITY_CONFIG_DIR` or `~/.gemini/antigravity`; local `./.agent` | GSD owns generated skills/agents/hooks and GSD settings entries only | Public Antigravity install/config docs for this file layout were not stable or complete as of 2026-05-11; installer compatibility therefore uses GSD's Gemini-compatible settings policy, documented shim baseline. |
| Cursor | Skills in `skills/gsd-*/SKILL.md`; agents in `agents/`; rule references under `rules/` | Global `CURSOR_CONFIG_DIR` or `~/.cursor`; local `./.cursor` | GSD owns generated skills/agents and GSD rule files or references; no hook/statusline ownership | [Cursor rules](https://docs.cursor.com/context/rules); docs not versioned, checked 2026-05-11 |
| Windsurf | Skills in `skills/gsd-*/SKILL.md`; agents in `agents/`; rule references under `rules/` | Global `WINDSURF_CONFIG_DIR` or `~/.codeium/windsurf`; local `./.windsurf` | GSD owns generated skills/agents and GSD rule files or references; no hook/statusline ownership | Windsurf public rule docs were source-limited in search results as of 2026-05-11; installer targets the common workspace rules convention `./.windsurf/rules` and must be rechecked before migrations rewrite rules |

View File

@@ -96,7 +96,9 @@ export function getGlobalConfigDir(runtime: string, explicitDir?: string | null)
// ── Copilot (VS Code) ────────────────────────────────────────────────────
case 'copilot':
return env['COPILOT_CONFIG_DIR'] ? expandTilde(env['COPILOT_CONFIG_DIR']) : path.join(home, '.copilot');
if (env['COPILOT_CONFIG_DIR']) return expandTilde(env['COPILOT_CONFIG_DIR']);
if (env['COPILOT_HOME']) return expandTilde(env['COPILOT_HOME']);
return path.join(home, '.copilot');
// ── Antigravity ──────────────────────────────────────────────────────────
case 'antigravity':

View File

@@ -63,7 +63,7 @@ describe('bug #3126: runtime-homes getGlobalConfigDir — defaults', () => {
test(`${runtime} default configDir`, () => {
// Clear all env vars for this runtime
const envKeys = ['CLAUDE_CONFIG_DIR','CURSOR_CONFIG_DIR','GEMINI_CONFIG_DIR',
'CODEX_HOME','COPILOT_CONFIG_DIR','ANTIGRAVITY_CONFIG_DIR','WINDSURF_CONFIG_DIR',
'CODEX_HOME','COPILOT_CONFIG_DIR','COPILOT_HOME','ANTIGRAVITY_CONFIG_DIR','WINDSURF_CONFIG_DIR',
'AUGMENT_CONFIG_DIR','TRAE_CONFIG_DIR','QWEN_CONFIG_DIR','HERMES_HOME',
'CODEBUDDY_CONFIG_DIR','CLINE_CONFIG_DIR','OPENCODE_CONFIG_DIR','OPENCODE_CONFIG',
'KILO_CONFIG_DIR','KILO_CONFIG',

View File

@@ -78,9 +78,11 @@ describe('getDirName (Copilot)', () => {
describe('getGlobalConfigDir (Copilot)', () => {
let originalCopilotConfigDir;
let originalCopilotHome;
beforeEach(() => {
originalCopilotConfigDir = process.env.COPILOT_CONFIG_DIR;
originalCopilotHome = process.env.COPILOT_HOME;
});
afterEach(() => {
@@ -89,10 +91,16 @@ describe('getGlobalConfigDir (Copilot)', () => {
} else {
delete process.env.COPILOT_CONFIG_DIR;
}
if (originalCopilotHome !== undefined) {
process.env.COPILOT_HOME = originalCopilotHome;
} else {
delete process.env.COPILOT_HOME;
}
});
test('returns ~/.copilot with no env var or explicit dir', () => {
delete process.env.COPILOT_CONFIG_DIR;
delete process.env.COPILOT_HOME;
const result = getGlobalConfigDir('copilot');
assert.strictEqual(result, path.join(os.homedir(), '.copilot'));
});
@@ -114,6 +122,34 @@ describe('getGlobalConfigDir (Copilot)', () => {
assert.strictEqual(result, '/explicit/path');
});
test('respects COPILOT_HOME env var', () => {
delete process.env.COPILOT_CONFIG_DIR;
process.env.COPILOT_HOME = '/custom/copilot-home';
const result = getGlobalConfigDir('copilot');
assert.strictEqual(result, '/custom/copilot-home');
});
test('COPILOT_HOME supports tilde expansion', () => {
delete process.env.COPILOT_CONFIG_DIR;
process.env.COPILOT_HOME = '~/my-copilot';
const result = getGlobalConfigDir('copilot');
assert.strictEqual(result, path.join(os.homedir(), 'my-copilot'));
});
test('COPILOT_CONFIG_DIR takes priority over COPILOT_HOME', () => {
process.env.COPILOT_CONFIG_DIR = '/config-dir-path';
process.env.COPILOT_HOME = '/home-path';
const result = getGlobalConfigDir('copilot');
assert.strictEqual(result, '/config-dir-path');
});
test('explicit dir takes priority over COPILOT_HOME', () => {
delete process.env.COPILOT_CONFIG_DIR;
process.env.COPILOT_HOME = '/home-path';
const result = getGlobalConfigDir('copilot', '/explicit/path');
assert.strictEqual(result, '/explicit/path');
});
test('does not break existing runtimes', () => {
assert.strictEqual(getGlobalConfigDir('claude'), path.join(os.homedir(), '.claude'));
assert.strictEqual(getGlobalConfigDir('codex'), path.join(os.homedir(), '.codex'));

View File

@@ -69,7 +69,7 @@ describe('getGlobalConfigDir — all runtimes default paths', () => {
// Test the default (no env var, no explicit dir) for each runtime
const ENV_KEYS = [
'CLAUDE_CONFIG_DIR', 'CURSOR_CONFIG_DIR', 'GEMINI_CONFIG_DIR', 'CODEX_HOME',
'GROK_AGENTS_HOME', 'COPILOT_CONFIG_DIR', 'WINDSURF_CONFIG_DIR', 'AUGMENT_CONFIG_DIR',
'GROK_AGENTS_HOME', 'COPILOT_CONFIG_DIR', 'COPILOT_HOME', 'WINDSURF_CONFIG_DIR', 'AUGMENT_CONFIG_DIR',
'TRAE_CONFIG_DIR', 'QWEN_CONFIG_DIR', 'HERMES_HOME', 'CODEBUDDY_CONFIG_DIR',
'CLINE_CONFIG_DIR', 'OPENCODE_CONFIG_DIR', 'OPENCODE_CONFIG', 'KILO_CONFIG_DIR',
'KILO_CONFIG', 'ANTIGRAVITY_CONFIG_DIR', 'XDG_CONFIG_HOME',