chatboxai/chatbox · error · Error
Token refresh response missing access_token
Error message
Token refresh response missing access_token
What it means
Thrown by OpenAI's refreshToken() when the refresh POST returned HTTP 2xx but the JSON body has no access_token field. This guards a malformed/unexpected success response: OpenAI's contract requires access_token on refresh, so its absence signals an API contract change, a non-JSON body parsed loosely, or a partial response.
Source
Thrown at src/main/oauth/providers/openai.ts:115
refresh_token: credentials.refreshToken,
client_id: CLIENT_ID,
}),
})
if (!response.ok) {
const text = await response.text()
log.error('[OAuth:OpenAI] Token refresh failed:', text)
throw new Error(`Token refresh failed: ${response.status}`)
}
const data = (await response.json()) as {
access_token?: string
refresh_token?: string
expires_in?: number
}
if (!data.access_token) {
throw new Error('Token refresh response missing access_token')
}
return {
accessToken: data.access_token,
refreshToken: data.refresh_token || credentials.refreshToken,
expiresAt: typeof data.expires_in === 'number' ? Date.now() + data.expires_in * 1000 - 5 * 60 * 1000 : undefined,
extra: {
accountId: extractAccountId(data.access_token),
},
}
},
}
async function exchangeCodeForTokens(code: string, verifier: string): Promise<OAuthCredentials> {
const response = await fetch(TOKEN_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({View on GitHub (pinned to 81571269ad)
Solutions
- Log the full response body when access_token is absent to detect contract drift or captive portals.
- Treat as a hard failure — do not return credentials with a missing access_token; trigger an interactive login().
- If the cause is a captive portal, detect non-JSON Content-Type before parsing and surface a network error instead.
Example fix
// before
const data = (await response.json()) as { access_token?: string; ... }
if (!data.access_token) {
throw new Error('Token refresh response missing access_token')
}
// after
const data = (await response.json()) as { access_token?: string; ... }
if (!data.access_token) {
log.error('[OAuth:OpenAI] refresh 2xx but no access_token', JSON.stringify(data))
throw new ReauthRequiredError('OpenAI refresh response malformed — re-login required')
} Defensive patterns
Strategy: type-guard
Type guard
function isOpenAIRefreshResponse(data: unknown): data is { access_token: string; refresh_token?: string; expires_in?: number } {
return typeof data === 'object' && data !== null && typeof (data as any).access_token === 'string' && (data as any).access_token.length > 0
} Try / catch
try {
return await provider.refreshToken(credentials)
} catch (e) {
if (/missing access_token/i.test(String(e))) {
// 2xx but malformed — likely captive portal or contract drift; re-login
await clearStoredCredentials()
throw new ReauthRequiredError('OpenAI refresh response malformed — re-login required')
}
throw e
} Prevention
- Check Content-Type is JSON before parsing; treat non-JSON 200s (captive portals) as network errors.
- Log the parsed body when access_token is absent to detect contract drift quickly.
- Never return credentials with an undefined access_token — always re-login.
When it happens
Trigger: OpenAI changed the response shape (field renamed); the body was HTML or empty (e.g. captive portal returning 200); a proxy stripped the body; the response was a partial JSON object missing access_token.
Common situations: Captive portal returning a 200 login page; OpenAI contract drift after an auth endpoint update; middleware corrupting the body; race where response.json() parsed an incomplete stream.
Related errors
- No refresh token available
- Token exchange response missing access_token
- No refresh token available
- Token refresh failed: ${response.status}
- No authorization code found in the input
AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12).
Data as JSON: /api/errors/e770bd82ec648761.
Report an issue: GitHub.