fix(installer): honor CLAUDE_CONFIG_DIR and CODEX_HOME (#1627) (#1783)

* fix(installer): honor CODEX_HOME for the Codex global install (#1627)

Codex resolves its user layer from `CODEX_HOME` and only falls back to
`~/.codex`. The target hardcoded the fallback, so a user on a custom profile
got a correct install into a directory Codex never reads — the MCP entry, the
AGENTS.md block, and detect() all pointed at the wrong profile, and the
failure is silent.

Resolve the global config dir from `CODEX_HOME` when set and non-blank,
mirroring what the copilot-cli target already does for `COPILOT_HOME`. Only
the user layer moves; the project layer (#1531) stays anchored to the project.

The test harness now also clears `CODEX_HOME` in setHome() alongside
HERMES_HOME/COPILOT_HOME — without that, the existing codex tests fail on a
developer machine that has the variable exported.

Note this is only half of #1627: the CLAUDE_CONFIG_DIR half is already
covered by the open PR #1029, which this deliberately does not touch.

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

* fix(installer): honor CLAUDE_CONFIG_DIR for global Claude installs

Build on #1633 by @maxmilian and port the CLAUDE_CONFIG_DIR approach
from #1029 by @borfast onto the current installer. Keep the CODEX_HOME
cherry-pick cbb08231 intact.

Resolve non-blank Claude profile paths with path.resolve. Put the global
MCP JSON inside a custom profile while preserving ~/.claude.json for the
default profile. Settings, instructions, detection, and uninstall follow
the selected profile; local installs keep their existing paths.

Clear and restore CLAUDE_CONFIG_DIR in the setHome test harness. Cover
absolute and relative profiles, idempotency, unset/empty/blank fallback,
default-profile preservation, detection/uninstall, and local installs.
Combine the Unreleased note for both environment variables.

Thanks @seanchann for reporting the issue.

Validation on Linux with Node 22.19.0:
- npx tsc -p tsconfig.json
- npx vitest run __tests__/installer-targets.test.ts: 245 passed, 3 skipped
- Reproduced both failures against main e720f6ca; the rebuilt CLI writes
  all files into CLAUDE_CONFIG_DIR and CODEX_HOME with no ~/.claude,
  ~/.claude.json, or ~/.codex created.

Fixes #1627

---------

Co-authored-by: Max Hsu <maxmilian@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
Colby Mchenry
2026-09-08 12:17:06 -05:00
committed by GitHub
co-authored by Max Hsu Claude Colby McHenry
parent e720f6ca53
commit 2f8cce5c57
4 changed files with 246 additions and 9 deletions
+12 -4
View File
@@ -7,7 +7,8 @@
* - Instructions to `AGENTS.md`.
*
* Both locations are supported (#1531):
* - global: `~/.codex/config.toml` + `~/.codex/AGENTS.md`
* - global: `$CODEX_HOME/config.toml` + `$CODEX_HOME/AGENTS.md`,
* falling back to `~/.codex` when the env var is unset (#1627)
* - local: `<cwd>/.codex/config.toml` + `<cwd>/AGENTS.md`
*
* Codex has a first-class project config layer: `.codex/config.toml`
@@ -53,9 +54,16 @@ import { buildTomlTable, removeTomlTable, upsertTomlTable } from './toml';
const TOML_HEADER = 'mcp_servers.codegraph';
function configDir(loc: Location): string {
return loc === 'global'
? path.join(os.homedir(), '.codex')
: path.join(process.cwd(), '.codex');
if (loc !== 'global') return path.join(process.cwd(), '.codex');
// Codex resolves its user layer from `CODEX_HOME` and only falls back to
// `~/.codex` (#1627). Installing to the fallback while Codex reads the
// override is a silent no-op: the files are written, and Codex never looks
// at them. Same resolution the copilot-cli target already does for
// `COPILOT_HOME`. Only the user layer moves — the project layer below is
// anchored to the project, not the profile.
const override = process.env.CODEX_HOME;
if (override && override.trim().length > 0) return override;
return path.join(os.homedir(), '.codex');
}
function tomlConfigPath(loc: Location): string {
return path.join(configDir(loc), 'config.toml');