paperclipai/paperclip · error

codex auth cache: account_id is not a valid account handle

Error message

codex auth cache: account_id is not a valid account handle

What it means

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.

Source

Thrown at packages/adapters/codex-local/src/server/codex-auth-cache.ts:132

/**
 * Resolves the entry path for one identity: `<cacheRoot>/<safeAccountId>/auth.json`.
 * The `account_id` is validated first by {@link toAccountHandle} (a strict
 * allowlist), the entry point of this function, then sanitized again by
 * {@link toCacheKey} (a denylist) as a second, independent layer. After the
 * join, this verifies the resolved entry path stays under the cache root and
 * ends at exactly `<safeAccountId>/auth.json`. This function does no filesystem
 * work; it is safe for a read path (the vend and the clear). (Security
 * condition 3.)
 */
export function resolveCodexAuthCacheEntryPath(
  env: NodeJS.ProcessEnv = process.env,
  accountId: string,
  companyId: string,
): string {
  const handle = toAccountHandle(accountId);
  if (!handle) {
    throw new Error("codex auth cache: account_id is not a valid account handle");
  }
  const resolvedRoot = resolveCodexAuthCacheDir(env, companyId);
  const safeKey = toCacheKey(handle);
  const entryDir = path.resolve(resolvedRoot, safeKey);
  const entryPath = path.resolve(entryDir, CACHE_ENTRY_FILE);
  const expectedEntryPath = path.join(resolvedRoot, safeKey, CACHE_ENTRY_FILE);
  if (
    !entryDir.startsWith(resolvedRoot + path.sep) ||
    path.dirname(entryDir) !== resolvedRoot ||
    entryPath !== expectedEntryPath
  ) {
    throw new Error("codex auth cache: resolved entry path escapes the cache root");
  }
  return entryPath;
}

/**
 * Ensures one directory exists and is private (mode 0700). Fails closed with

View on GitHub (pinned to 01ad858492)

Solutions

  1. Verify the accountId passed in is the raw Codex account_id from the auth payload, not a label or email.
  2. Check that the auth source actually contains a non-empty account_id; re-login to Codex if it is missing.
  3. Normalize/strip unsafe characters from the id before calling, or skip the cache lookup when no valid id exists.
  4. Compare against toAccountHandle() in codex-auth-cache.ts to confirm which characters are accepted.

Example fix

// before
const entryPath = resolveCodexAuthCacheEntryPath(env, auth.accountId ?? "", companyId);
// after
if (!auth.accountId || toAccountHandle(auth.accountId) === null) {
  return null; // no valid cached entry possible
}
const entryPath = resolveCodexAuthCacheEntryPath(env, auth.accountId, companyId);
Defensive patterns

Strategy: validation

Validate before calling

import { toAccountHandle } from "./account-handle";
function hasCacheableAccountId(accountId: string | undefined): boolean {
  return accountId !== undefined && toAccountHandle(accountId) !== null;
}

Type guard

function isCacheableAccountId(v: unknown): v is string {
  return typeof v === "string" && v.length > 0 && toAccountHandle(v) !== null;
}

Try / catch

try {
  const entry = resolveCodexAuthCacheEntryPath(env, accountId, companyId);
  return readAuthCacheEntry(entry);
} catch (err) {
  if (err instanceof Error && err.message.includes("not a valid account handle")) {
    return null; // no cacheable identity; fall through to fresh login
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/a79fe67fe0e09b9f. Report an issue: GitHub.