{"record":{"id":"31b636c7c95318e6","repo":"Yeachan-Heo/oh-my-codex","slug":"auth-path-is-not-a-directory-dir","errorCode":null,"errorMessage":"auth path is not a directory: ${dir}","messagePattern":"auth path is not a directory: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/auth/paths.ts","lineNumber":58,"sourceCode":"\nexport function resolveDefaultCodexHome(home = homedir()): string {\n  return join(home, \".codex\");\n}\n\nexport function resolveLiveAuthPath(\n  cwd = process.cwd(),\n  env: NodeJS.ProcessEnv = process.env,\n  home = homedir(),\n): string {\n  const codexHome = resolveCodexHomeForLaunch(cwd, env) || resolveDefaultCodexHome(home);\n  return join(codexHome, \"auth.json\");\n}\n\nexport async function ensurePrivateDir(dir: string): Promise<void> {\n  await mkdir(dir, { recursive: true, mode: AUTH_DIR_MODE });\n  const info = await lstat(dir);\n  if (info.isSymbolicLink()) throw new Error(`auth directory must not be a symlink: ${dir}`);\n  if (!info.isDirectory()) throw new Error(`auth path is not a directory: ${dir}`);\n  if (process.platform !== \"win32\") {\n    const { chmod } = await import(\"fs/promises\");\n    await chmod(dir, AUTH_DIR_MODE).catch(() => undefined);\n  }\n}\n\nexport async function assertReadableFile(path: string, label: string): Promise<void> {\n  try {\n    const info = await stat(path);\n    if (!info.isFile()) throw new Error(`${label} is not a file: ${path}`);\n  } catch (err) {\n    if (err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\") {\n      throw new Error(`${label} not found: ${path}`);\n    }\n    throw err;\n  }\n}\n","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/auth/paths.ts#L40-L76","documentation":"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.","triggerScenarios":"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.","commonSituations":"Respawn automation, multiple orchestrators mutating the same window, or stale topology snapshots persisted across tmux server restarts (tmux server restart resets pane ids).","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const proofs = readExactPaneProofsSync([...expectedPanePids.keys()]); proofs.every((p, i) => p.status === 'live' && p.pid === [...expectedPanePids.values()][i]);","typeGuard":null,"tryCatchPattern":"catch (err) { if (/pane identity changed/.test(err.message)) { /* topology invalid: recreate session, do not retry */ } }","preventionTips":["Never persist topology snapshots across tmux restarts","Single orchestrator per window","Re-freeze topology after any pane churn"],"tags":["tmux","pid-reuse","topology","stale-reference"],"backgroundTag":"stale-pane-reference","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}