{"record":{"id":"1d150e97f702055a","repo":"paperclipai/paperclip","slug":"codex-auth-cache-account-home-directory-no-longer","errorCode":null,"errorMessage":"codex auth cache: account-home directory no longer exists; refusing to write a secret that names it","messagePattern":"codex auth cache: account-home directory no longer exists; refusing to write a secret that names it","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/adapters/codex-local/src/server/codex-auth-cache.ts","lineNumber":355,"sourceCode":" * under the cache root can ever be an account-home directory, so this never\n * rejects an unrelated `local_encrypted` secret write, such as an API key or\n * a hand-typed token.\n */\nexport async function assertAccountHomeCacheDirStillValid(\n  env: NodeJS.ProcessEnv = process.env,\n  companyId: string,\n  value: string,\n): Promise<void> {\n  const cacheRoot = resolveCodexAuthCacheDir(env, companyId);\n  if (!value.startsWith(cacheRoot + path.sep)) return;\n  const exists = await lstat(value)\n    .then(() => true)\n    .catch((error: NodeJS.ErrnoException) => {\n      if (error.code === \"ENOENT\") return false;\n      throw error;\n    });\n  if (!exists) {\n    throw new Error(\n      \"codex auth cache: account-home directory no longer exists; refusing to write a secret that names it\",\n    );\n  }\n}\n\n/**\n * Reads the usable subscription `account_id` from an `auth.json` payload. Returns\n * `null` for an absent, unusable, or api-key credential (no subscription\n * identity). This mirrors `parseAuth` in `codex-auth-merge-decision.cjs`; keep\n * the two in step when the auth format changes.\n *\n * The returned value is the exact, untrimmed `account_id` string. A caller\n * passes it on to {@link toAccountHandle} unchanged: that function is the\n * one place that decides whether surrounding whitespace is acceptable, and it\n * must see the raw value to reject an identifier that differs from another\n * one only by whitespace. Trimming here, before that check runs, would let\n * two distinct identifiers collapse onto the same account handle.\n */","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/adapters/codex-local/src/server/codex-auth-cache.ts#L337-L373","documentation":"assertAccountHomeCacheDirStillValid() checks that the account-home directory still exists on disk before createManagedLocalSecret() writes a secret whose name references that directory. If stat fails with ENOENT the directory is gone, and writing a secret pointing at a nonexistent home would leave dangling, unusable credentials — so the write is refused.","triggerScenarios":"Calling createManagedLocalSecret() (directly or via secretService) after the account-home directory was deleted or moved — e.g. manual cleanup of ~/.codex or the cache root, an OS temp cleaner removing it, or a home migration that did not recreate the directory.","commonSituations":"User manually deleted the Codex home/account directory; disk cleanup tools purged cache dirs; the account was logged out elsewhere while the local secret store still references its home; workspace/container rebuilds wiping home directories but not secrets.","solutions":["Recreate the account home by re-running the Codex device login / promotion so the directory is re-established, then retry the secret write.","Verify the account-home directory path exists (fs.stat) and that the expected handle-derived directory is present before writing.","If the account is genuinely gone, remove the stale secret/cache entry so it no longer references a missing home.","Check nothing in your cleanup scripts or tmp cleaners deletes the Codex account-home directory while the adapter is using it."],"exampleFix":"// before\nawait createManagedLocalSecret(secretService, { accountHandle, ... });\n// after\nconst homeDir = resolveAccountHomeDir(accountHandle);\nif (!existsSync(homeDir)) {\n  throw new Error(`account home missing: ${homeDir}; re-run codex login before writing secrets`);\n}\nawait createManagedLocalSecret(secretService, { accountHandle, ... });","handlingStrategy":"try-catch","validationCode":"import { stat } from \"node:fs/promises\";\nasync function accountHomeExists(dir: string): Promise<boolean> {\n  try { await stat(dir); return true; } catch (e: any) { if (e.code === \"ENOENT\") return false; throw e; }\n}\nif (!(await accountHomeExists(homeDir))) {\n  await reRunCodexLogin(); // recreate home before any secret write\n}","typeGuard":null,"tryCatchPattern":"try {\n  await createManagedLocalSecret(secretService, params);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"account-home directory no longer exists\")) {\n    await reRunCodexLoginAndPromotion(); // rebuild home, then retry once\n    await createManagedLocalSecret(secretService, params);\n    return;\n  }\n  throw err;\n}","preventionTips":["Exclude the Codex account-home and auth-cache directories from tmp cleaners and cleanup cron jobs.","After any container/workspace rebuild, re-run device login before writing secrets.","Periodically reconcile stored secrets against existing account homes and prune stale entries.","Check home directory existence during startup health checks so the gap is caught before writes."],"tags":["filesystem","secrets","codex","state-consistency"],"backgroundTag":"file-not-found","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}