decolua/9router · error

Missing accessToken

Error message

Missing accessToken

What it means

After stripping server-controlled fields, each codex import item must carry a non-empty string accessToken (the ChatGPT/Codex OAuth access token). This check throws before the JWT backfill and connection creation, since a connection cannot be built without a token.

Source

Thrown at src/app/api/oauth/codex/bulk-import/route.js:73

  for (let i = 0; i < accounts.length; i++) {
    const raw = accounts[i];
    try {
      if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
        throw new Error("Item is not an object");
      }

      // Strip server-controlled fields
      const {
        id: _id,
        provider: _provider,
        authType: _authType,
        createdAt: _createdAt,
        updatedAt: _updatedAt,
        ...item
      } = raw;

      if (!item.accessToken || typeof item.accessToken !== "string") {
        throw new Error("Missing accessToken");
      }

      // Backfill missing identity fields from JWT claims
      const psd = item.providerSpecificData || {};
      const needsEmail = !item.email;
      const needsAccountId = !psd.chatgptAccountId;
      const needsPlanType = !psd.chatgptPlanType;

      if (needsEmail || needsAccountId || needsPlanType) {
        const info = extractCodexAccountInfo(item.idToken || item.accessToken) || {};
        if (needsEmail && info.email) item.email = info.email;
        if (needsAccountId && info.chatgptAccountId) {
          psd.chatgptAccountId = info.chatgptAccountId;
        }
        if (needsPlanType && info.chatgptPlanType) {
          psd.chatgptPlanType = info.chatgptPlanType;
        }
      }

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Add an accessToken string field to each item (snake_case access_token is not accepted here).
  2. If you only have access_token, rename the key to accessToken before importing.
  3. Re-export the accounts from the source tool ensuring tokens are included and not redacted.

Example fix

// before
{ "accounts": [{ "access_token": "eyJ..." }] }
// after
{ "accounts": [{ "accessToken": "eyJ..." }] }
Defensive patterns

Strategy: validation

Validate before calling

for (const a of accounts) {
  if (!a?.accessToken || typeof a.accessToken !== "string") {
    throw new Error("each item needs a string accessToken");
  }
}

Type guard

const hasAccessToken = (x) =>
  typeof x?.accessToken === "string" && x.accessToken.length > 0;

Try / catch

try {
  await bulkImport(items);
} catch (e) {
  if (e.message === "Missing accessToken") {
    const fixed = items.map(({ access_token, ...r }) =>
      ({ accessToken: access_token ?? r.accessToken, ...r }));
  } else throw e;
}

Prevention

When it happens

Trigger: An accounts[] item in the codex bulk-import POST that lacks accessToken, has it under a different key (e.g. access_token), or has a non-string value (null, number, object).

Common situations: Import file exported from a tool using snake_case access_token instead of accessToken, redacted/stripped tokens from a shared config, or only refreshToken present.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/f29b795752112d75. Report an issue: GitHub.