Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions src/codex/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,35 @@ export function findWslWindowsCodexHome(deps: CodexHomeDeps = {}): string | null
return candidates.length === 1 ? candidates[0]! : null;
}

function canonicalExistingCodexHome(path: string, deps: CodexHomeDeps): string {
const stat = deps.statSync ?? statSync;
try {
if (!stat(path).isDirectory()) return path;
// Use the portable resolver here rather than realpathSync.native. The
// Windows Bun standalone runtime can reject a valid junction through the
// native resolver, while the effective home still needs to be physical
// before callers open auth.json or native-profile state.
return (deps.realpathSync ?? realpathSync)(path);
} catch {
// Preserve the existing lexical-path behavior for missing or unreadable
// homes; callers that require the directory still fail at their boundary.
return path;
}
}

export function defaultCodexHome(deps: CodexHomeDeps = {}): string {
const home = (deps.homedir ?? homedir)();
const defaultHome = join(home, ".codex");
const canonicalDefaultHome = canonicalExistingCodexHome(defaultHome, deps);
// A local ~/.codex that Codex is already using is the user's Codex home even before
// config.toml exists (a fresh install: login writes auth.json, first use writes
// sessions/ and history.jsonl). A local directory with none of that state is not
// evidence of a local Codex: before #5441 such a home let WSL discovery pick the
// Windows home, and existing WSL users who run against that Windows home must not
// be moved to an empty local one on upgrade.
if (localCodexHomeIsDirectory(defaultHome, deps) && localCodexHomeInUse(defaultHome, deps)) return defaultHome;
return findWslWindowsCodexHome(deps) ?? defaultHome;
// be moved to an empty local one on upgrade. Return the canonical path so a
// Windows junction cannot leak into later credential and profile writes.
if (localCodexHomeIsDirectory(defaultHome, deps) && localCodexHomeInUse(defaultHome, deps)) return canonicalDefaultHome;
return findWslWindowsCodexHome(deps) ?? canonicalDefaultHome;
}

function localCodexHomeInUse(home: string, deps: CodexHomeDeps): boolean {
Expand Down
21 changes: 20 additions & 1 deletion tests/codex-integration/codex-home-wsl.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test";
import { mkdtempSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, realpathSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { defaultCodexHome, wslAutomountRoot, listWslWindowsCodexHomes } from "../../src/codex/home";
Expand Down Expand Up @@ -42,6 +43,24 @@ describe("wsl.conf automount root", () => {
expect(wslAutomountRoot({ wslConf: "[automount]\nenabled = true\n" })).toBe("/mnt");
});

test.skipIf(process.platform !== "win32")("canonicalizes a junction-backed default Codex home", () => {
const root = mkdtempSync(join(tmpdir(), "ocx-codex-home-junction-"));
const target = join(root, "codex-target");
const link = join(root, ".codex");
try {
mkdirSync(target);
writeFileSync(join(target, "auth.json"), "{}\n", { encoding: "utf8" });
const result = spawnSync("cmd.exe", ["/d", "/c", "mklink", "/J", link, target], {
encoding: "utf8",
windowsHide: true,
});
expect(result.status).toBe(0);
expect(defaultCodexHome({ homedir: () => root, env: {} })).toBe(realpathSync(target));
} finally {
removeTreeWithRetry(root);
}
});

test("parses a custom root with quotes, comments, and trailing slashes", () => {
expect(wslAutomountRoot({ wslConf: "[automount]\nroot = /custom\n" })).toBe("/custom");
expect(wslAutomountRoot({ wslConf: "[automount]\nroot = \"/custom/\" # comment\n" })).toBe("/custom");
Expand Down
Loading