paperclipai/paperclip · error · Error

device-login promotion: the account identifier cannot form a

Error message

device-login promotion: the account identifier cannot form a valid account handle

What it means

During device-login promotion, the adapter converts the Codex account identity into a sanitized account handle via toAccountHandle(). That handle names the account's home directory and the company secret, so an identity that cannot produce a safe handle is rejected before any filesystem or secret-store write occurs. This is a fail-fast invariant guarding against unsafe or malformed identifiers.

Source

Thrown at packages/adapters/codex-local/src/server/adapter-auth-promotion.ts:226

  }

  // 2. Validate the credential with the export rules. This rejects an empty, an
  //    oversized, an API-key, a non-subscription, and a malformed payload.
  assertUsableSubscriptionShape(authBytes);
  const accountId = readSubscriptionAccountId(authBytes);
  if (!accountId) {
    // The shape gate above already guarantees a subscription identity; this guard
    // keeps the account_id non-null for the handle conversion without a non-null
    // cast.
    throw new Error("device-login promotion: the credential has no subscription identity");
  }

  // 2b. Convert the identity into a safe account handle. The handle names both
  //     this account's own home directory and its company secret, so a login
  //     whose identity cannot form one must fail before any write.
  const accountHandle = toAccountHandle(accountId);
  if (!accountHandle) {
    throw new Error("device-login promotion: the account identifier cannot form a valid account handle");
  }

  // 3. Decision C: only a user-initiated login seeds a home.
  if (!userInitiated) {
    await log("[paperclip] Codex device-login promotion: skipped (an automatic background login never seeds a home).");
    return { outcome: "background_skipped", accountId, accountHomeDir: null, accountHomeCreated: false };
  }

  // 4. Decision H: write only while the session still owns the active slot.
  const soleOwner = await isSoleActiveOwner();
  if (!soleOwner) {
    await log("[paperclip] Codex device-login promotion: skipped (the session no longer holds the sole active claim on the slot).");
    return { outcome: "not_sole_owner", accountId, accountHomeDir: null, accountHomeCreated: false };
  }

  // 5a. This account's own home is the durable result of a login: each account
  //     handle addresses exactly one home, so this write can never collide with
  //     a different identity, and a write failure here fails the whole

View on GitHub (pinned to 01ad858492)

Solutions

  1. Inspect the Codex auth source the accountId came from and confirm the account_id field is populated and well-formed.
  2. Re-run the Codex device login so a fresh, valid account identity is produced, then retry promotion.
  3. Sanitize or normalize the identifier before calling promoteDeviceLoginCredential() so toAccountHandle() succeeds.
  4. If the id is structurally valid but still rejected, check the toAccountHandle implementation for the exact accepted character set and align your id.

Example fix

// before
await promoteDeviceLoginCredential({ accountId: auth.lastRefreshAccountId ?? "", ... });
// after
const accountId = auth.lastRefreshAccountId;
if (!accountId || toAccountHandle(accountId) === null) {
  throw new Error(`codex auth: unusable account identifier: ${JSON.stringify(auth.lastRefreshAccountId)}`);
}
await promoteDeviceLoginCredential({ accountId, ... });
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

try {
  await promoteDeviceLoginCredential({ accountId, ... });
} catch (err) {
  if (err instanceof Error && err.message.includes("cannot form a valid account handle")) {
    log("codex account id unusable; re-run device login");
    return { outcome: "invalid_account_id" };
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling promoteDeviceLoginCredential() with an accountId whose shape cannot be sanitized by toAccountHandle() — e.g. an empty string, a string of only unsafe characters, or an identifier containing path separators/segments that would normalize away.

Common situations: Auth JSON from a Codex install missing or corrupting the account id field; parsing an auth payload where account_id was never set (e.g. a fresh/anonymous login); a version change upstream altering the id format; programmatic promotion with a placeholder or empty identifier.

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/0d40b8502ec3d0e5. Report an issue: GitHub.