Yeachan-Heo/oh-my-codex · error · Error
auth path is not a directory: ${dir}
Error message
auth path is not a directory: ${dir} What it means
In the final batch proof pass of requireFrozenWindowTopologySync, a pane is live but either its returned pane id or PID doesn't match the frozen expectation. This is the definitive signal that the pane identity was recycled or the proof read raced a pane swap, so the frozen contract is void.
Source
Thrown at src/auth/paths.ts:58
export function resolveDefaultCodexHome(home = homedir()): string {
return join(home, ".codex");
}
export function resolveLiveAuthPath(
cwd = process.cwd(),
env: NodeJS.ProcessEnv = process.env,
home = homedir(),
): string {
const codexHome = resolveCodexHomeForLaunch(cwd, env) || resolveDefaultCodexHome(home);
return join(codexHome, "auth.json");
}
export async function ensurePrivateDir(dir: string): Promise<void> {
await mkdir(dir, { recursive: true, mode: AUTH_DIR_MODE });
const info = await lstat(dir);
if (info.isSymbolicLink()) throw new Error(`auth directory must not be a symlink: ${dir}`);
if (!info.isDirectory()) throw new Error(`auth path is not a directory: ${dir}`);
if (process.platform !== "win32") {
const { chmod } = await import("fs/promises");
await chmod(dir, AUTH_DIR_MODE).catch(() => undefined);
}
}
export async function assertReadableFile(path: string, label: string): Promise<void> {
try {
const info = await stat(path);
if (!info.isFile()) throw new Error(`${label} is not a file: ${path}`);
} catch (err) {
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
throw new Error(`${label} not found: ${path}`);
}
throw err;
}
}
View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Never persist expectedPanePids across tmux server restarts — re-capture after any restart
- Disable respawn/auto-recreation for team-managed panes
- Recreate the team session to re-freeze a consistent topology
- Check for a second concurrent team session touching the same window
Defensive patterns
Strategy: validation
Validate before calling
const proofs = readExactPaneProofsSync([...expectedPanePids.keys()]); proofs.every((p, i) => p.status === 'live' && p.pid === [...expectedPanePids.values()][i]);
Try / catch
catch (err) { if (/pane identity changed/.test(err.message)) { /* topology invalid: recreate session, do not retry */ } } Prevention
- Never persist topology snapshots across tmux restarts
- Single orchestrator per window
- Re-freeze topology after any pane churn
When it happens
Trigger: Pane id/PID churn (respawn, pane recreation, window swap) occurring between topology freeze, first validation, and final batch proof; or expectedPanePids built from a different session incarnation.
Common situations: Respawn automation, multiple orchestrators mutating the same window, or stale topology snapshots persisted across tmux server restarts (tmux server restart resets pane ids).
Related errors
- auth directory must not be a symlink: ${dir}
- ${label} is not a file: ${path}
- ${label} must not be a symlink: ${path}
- Cannot use out-of-scope artifact path; ${evidenceDescription
- Refusing to overwrite existing ${repoRelative(cwd, missionPa
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/31b636c7c95318e6.
Report an issue: GitHub.