coleam00/Archon · error

OpenAI token ${operation} response missing access_token/expi

Error message

OpenAI token ${operation} response missing access_token/expires_in.

What it means

credentialsFromTokenResponse validates the JSON returned by OpenAI's OAuth token endpoint during an authorization-code exchange or refresh. It throws when the response lacks a usable access_token (non-empty string) or expires_in (finite number), meaning OpenAI's reply did not contain the fields needed to build a credential. Throwing here prevents storing a half-formed credential that would fail later at request time.

Source

Thrown at packages/core/src/credentials/openai-oauth.ts:232

  }
  return raw as OpenAiTokenResponse;
}

/**
 * Map a token response onto the stored credential blob. Fails loud on a
 * missing `id_token` at exchange time (the whole point of owning this flow);
 * on refresh, a response that omits `id_token`/`refresh_token` PRESERVES the
 * previous values instead of degrading the blob.
 */
function credentialsFromTokenResponse(
  json: OpenAiTokenResponse,
  operation: 'exchange' | 'refresh',
  previous?: OAuthCredentials
): OpenAiOAuthCredentials {
  const access = typeof json.access_token === 'string' ? json.access_token : '';
  const expiresIn = typeof json.expires_in === 'number' ? json.expires_in : NaN;
  if (!access || !Number.isFinite(expiresIn)) {
    throw new Error(`OpenAI token ${operation} response missing access_token/expires_in.`);
  }
  const prevRefresh = typeof previous?.refresh === 'string' ? previous.refresh : '';
  const refresh = typeof json.refresh_token === 'string' ? json.refresh_token : prevRefresh;
  if (!refresh) {
    throw new Error(`OpenAI token ${operation} response missing refresh_token.`);
  }
  const prevIdToken = typeof previous?.id_token === 'string' ? previous.id_token : '';
  const idToken = typeof json.id_token === 'string' && json.id_token ? json.id_token : prevIdToken;
  if (!idToken) {
    // Fail loud: an id_token-less credential reproduces the exact #1924
    // breakage ("invalid ID token format" in the Codex CLI) — never store one.
    throw new Error(
      `OpenAI token ${operation} response did not include an id_token (required by the Codex CLI).`
    );
  }
  const prevAccountId = typeof previous?.accountId === 'string' ? previous.accountId : '';
  const accountId = accountIdFromAccessToken(access) ?? prevAccountId;
  if (!accountId) {

View on GitHub (pinned to 0773b97458)

Solutions

  1. Log the full response body (redacting secrets) to see what OpenAI actually returned.
  2. Retry the OAuth flow from scratch: the authorization code may be expired or already consumed — start a new PKCE login.
  3. Check for a proxy/interceptor altering the token endpoint response (custom OPENAI_BASE_URL, corporate MITM proxy).
  4. Verify you are POSTing to https://auth.openai.com/oauth/token with grant_type=authorization_code and the correct client_id.
  5. If it persists across attempts, check OpenAI status/Auth0 tenant issues and the library's pinned client config for staleness.

Example fix

// before: passing raw response without checking
const creds = await exchangeOpenAiAuthorizationCode(res.json());
// after: inspect and fail on non-token responses
const json = await res.json();
if (json.error) throw new Error(`Token endpoint error: ${json.error} - ${json.error_description}`);
const creds = await exchangeOpenAiAuthorizationCode(json);
Defensive patterns

Strategy: validation

Validate before calling

const looksLikeTokenResponse = (j) =>
  j && typeof j.access_token === 'string' && j.access_token.length > 0 &&
  Number.isFinite(j.expires_in);
const json = await res.json();
if (json?.error) throw new Error(`token endpoint: ${json.error}: ${json.error_description}`);
if (!looksLikeTokenResponse(json)) throw new Error('unexpected token response shape: ' + JSON.stringify(Object.keys(json ?? {})));

Type guard

function isTokenResponse(j: unknown): j is { access_token: string; expires_in: number } {
  const o = j as Record<string, unknown>;
  return !!o && typeof o.access_token === 'string' && o.access_token !== '' &&
    typeof o.expires_in === 'number' && Number.isFinite(o.expires_in);
}

Try / catch

try {
  const creds = await exchangeOpenAiAuthorizationCode(code, verifier);
} catch (e) {
  if (e.message.includes('missing access_token/expires_in')) {
    logRawTokenResponse(lastResponseBody); // inspect, redact secrets
    throw new Error('OpenAI token endpoint returned a non-token response; restart the OAuth login');
  }
  throw e;
}

Prevention

When it happens

Trigger: An OpenAI token exchange or refresh HTTP response body parses to JSON that is missing access_token or has a non-string access_token, or missing/non-numeric expires_in (e.g. expires_in: null or a string).

Common situations: OpenAI returns a 4xx error body (invalid_grant, expired code) that still parses as JSON; a proxy or captive portal returns HTML/JSON without token fields; OpenAI changes the token response shape; the caller accidentally passes the wrong endpoint's JSON.

Related errors


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/8c52d7d277aec289. Report an issue: GitHub.