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
- Log the full response body (redacting secrets) to see what OpenAI actually returned.
- Retry the OAuth flow from scratch: the authorization code may be expired or already consumed — start a new PKCE login.
- Check for a proxy/interceptor altering the token endpoint response (custom OPENAI_BASE_URL, corporate MITM proxy).
- Verify you are POSTing to https://auth.openai.com/oauth/token with grant_type=authorization_code and the correct client_id.
- 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
- Always check for `error`/`error_description` in the parsed token response before consuming it.
- Log (redacted) response bodies on failure to distinguish proxy/HTML responses from real token errors.
- Restart the OAuth flow after this error — codes are single-use and often expired.
- Avoid custom proxies on the token endpoint that can rewrite or strip fields.
- Pin and review the OpenAI client config (client_id, issuer URL) when upgrading.
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
- OpenAI token ${operation} response missing refresh_token.
- OpenAI token ${operation} response did not include an id_tok
- Failed to extract the ChatGPT account id from the OpenAI acc
- Stored OpenAI credential has no refresh token.
- OAuth state mismatch.
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/8c52d7d277aec289.
Report an issue: GitHub.