{"record":{"id":"a79fe67fe0e09b9f","repo":"paperclipai/paperclip","slug":"codex-auth-cache-account-id-is-not-a-valid-accoun","errorCode":null,"errorMessage":"codex auth cache: account_id is not a valid account handle","messagePattern":"codex auth cache: account_id is not a valid account handle","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/adapters/codex-local/src/server/codex-auth-cache.ts","lineNumber":132,"sourceCode":"\n/**\n * Resolves the entry path for one identity: `<cacheRoot>/<safeAccountId>/auth.json`.\n * The `account_id` is validated first by {@link toAccountHandle} (a strict\n * allowlist), the entry point of this function, then sanitized again by\n * {@link toCacheKey} (a denylist) as a second, independent layer. After the\n * join, this verifies the resolved entry path stays under the cache root and\n * ends at exactly `<safeAccountId>/auth.json`. This function does no filesystem\n * work; it is safe for a read path (the vend and the clear). (Security\n * condition 3.)\n */\nexport function resolveCodexAuthCacheEntryPath(\n  env: NodeJS.ProcessEnv = process.env,\n  accountId: string,\n  companyId: string,\n): string {\n  const handle = toAccountHandle(accountId);\n  if (!handle) {\n    throw new Error(\"codex auth cache: account_id is not a valid account handle\");\n  }\n  const resolvedRoot = resolveCodexAuthCacheDir(env, companyId);\n  const safeKey = toCacheKey(handle);\n  const entryDir = path.resolve(resolvedRoot, safeKey);\n  const entryPath = path.resolve(entryDir, CACHE_ENTRY_FILE);\n  const expectedEntryPath = path.join(resolvedRoot, safeKey, CACHE_ENTRY_FILE);\n  if (\n    !entryDir.startsWith(resolvedRoot + path.sep) ||\n    path.dirname(entryDir) !== resolvedRoot ||\n    entryPath !== expectedEntryPath\n  ) {\n    throw new Error(\"codex auth cache: resolved entry path escapes the cache root\");\n  }\n  return entryPath;\n}\n\n/**\n * Ensures one directory exists and is private (mode 0700). Fails closed with","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/adapters/codex-local/src/server/codex-auth-cache.ts#L114-L150","documentation":"resolveCodexAuthCacheEntryPath() builds the on-disk cache entry path from the account id. The id is first converted to a safe handle via toAccountHandle(); if that fails there is no safe directory key to use, so the function throws rather than constructing an unsafe path. This protects both path traversal safety and the handle-to-directory correspondence.","triggerScenarios":"Calling any of the auth-cache accessors (accountAuth, entryPath, resolveEntry, execute, prepareCodexHelloProbe) with an accountId that toAccountHandle() rejects — empty string, only unsafe characters, or ids containing '/' or '..' segments.","commonSituations":"A stale or corrupted Codex auth.json lacking a usable account id; passing a display name or email instead of the raw account id; upstream Codex changing id format; reading auth state before first login completes.","solutions":["Verify the accountId passed in is the raw Codex account_id from the auth payload, not a label or email.","Check that the auth source actually contains a non-empty account_id; re-login to Codex if it is missing.","Normalize/strip unsafe characters from the id before calling, or skip the cache lookup when no valid id exists.","Compare against toAccountHandle() in codex-auth-cache.ts to confirm which characters are accepted."],"exampleFix":"// before\nconst entryPath = resolveCodexAuthCacheEntryPath(env, auth.accountId ?? \"\", companyId);\n// after\nif (!auth.accountId || toAccountHandle(auth.accountId) === null) {\n  return null; // no valid cached entry possible\n}\nconst entryPath = resolveCodexAuthCacheEntryPath(env, auth.accountId, companyId);","handlingStrategy":"validation","validationCode":"import { toAccountHandle } from \"./account-handle\";\nfunction hasCacheableAccountId(accountId: string | undefined): boolean {\n  return accountId !== undefined && toAccountHandle(accountId) !== null;\n}","typeGuard":"function isCacheableAccountId(v: unknown): v is string {\n  return typeof v === \"string\" && v.length > 0 && toAccountHandle(v) !== null;\n}","tryCatchPattern":"try {\n  const entry = resolveCodexAuthCacheEntryPath(env, accountId, companyId);\n  return readAuthCacheEntry(entry);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"not a valid account handle\")) {\n    return null; // no cacheable identity; fall through to fresh login\n  }\n  throw err;\n}","preventionTips":["Guard every auth-cache lookup with a toAccountHandle() check before calling.","Never pass emails, display names, or trimmed ids into the cache API — use the raw account_id.","Treat a missing/invalid account_id as 'no cached auth' rather than an error condition in callers.","Log the offending raw value when this occurs to spot upstream format changes early."],"tags":["filesystem","validation","codex","path-safety"],"backgroundTag":"invalid-identifier-format","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}