* 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:
co-authored by
Max Hsu
Claude
Colby McHenry
parent
e720f6ca53
commit
2f8cce5c57
@@ -10,6 +10,9 @@
|
||||
* - Instructions to `~/.claude/CLAUDE.md` (global) or
|
||||
* `./.claude/CLAUDE.md` (local).
|
||||
*
|
||||
* A non-blank `CLAUDE_CONFIG_DIR` moves all three global files into
|
||||
* that profile directory, including `.claude.json` (#1627).
|
||||
*
|
||||
* Earlier versions wrote the local MCP entry to `./.claude.json` — a
|
||||
* file Claude Code never reads — so the server silently never loaded
|
||||
* until the user manually renamed it to `.mcp.json` (issue #207). We
|
||||
@@ -53,18 +56,31 @@ function getClaudeMcpServerConfig() {
|
||||
return { ...getMcpServerConfig(), alwaysLoad: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Root of the global Claude Code profile. Settings and instructions follow
|
||||
* CLAUDE_CONFIG_DIR; local installs stay anchored to the project (#1627).
|
||||
*/
|
||||
function globalConfigDir(): string {
|
||||
const override = process.env.CLAUDE_CONFIG_DIR;
|
||||
return override && override.trim().length > 0
|
||||
? path.resolve(override)
|
||||
: path.join(os.homedir(), '.claude');
|
||||
}
|
||||
function configDir(loc: Location): string {
|
||||
return loc === 'global'
|
||||
? path.join(os.homedir(), '.claude')
|
||||
? globalConfigDir()
|
||||
: path.join(process.cwd(), '.claude');
|
||||
}
|
||||
function mcpJsonPath(loc: Location): string {
|
||||
// global → ~/.claude.json (user scope: visible in every project).
|
||||
// global → $CLAUDE_CONFIG_DIR/.claude.json for a custom profile, else
|
||||
// ~/.claude.json (beside ~/.claude, not inside it). User scope: every project.
|
||||
// local → ./.mcp.json (project scope: the ONLY project-level MCP
|
||||
// file Claude Code reads — NOT ./.claude.json, which it ignores).
|
||||
return loc === 'global'
|
||||
? path.join(os.homedir(), '.claude.json')
|
||||
: path.join(process.cwd(), '.mcp.json');
|
||||
if (loc !== 'global') return path.join(process.cwd(), '.mcp.json');
|
||||
const override = process.env.CLAUDE_CONFIG_DIR;
|
||||
return override && override.trim().length > 0
|
||||
? path.join(path.resolve(override), '.claude.json')
|
||||
: path.join(os.homedir(), '.claude.json');
|
||||
}
|
||||
/**
|
||||
* Where pre-#207 installers wrote the local MCP entry. Claude Code
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user