* 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:
5
.changeset/812-copilot-home.md
Normal file
5
.changeset/812-copilot-home.md
Normal 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
@@ -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` |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user