chatboxai/chatbox · error · Error

Token exchange response missing access_token

Error message

Token exchange response missing access_token

What it means

Thrown by OpenAI's exchangeCodeForTokens() when the token endpoint returned HTTP 2xx but the JSON body has no access_token. This guards an unexpected success response — the exchange technically succeeded at the transport layer but the payload does not contain the credential the app needs.

Solutions

  1. Log the parsed body to detect contract drift or captive portals.
  2. Do not proceed without an access_token — trigger login() again.
  3. Check Content-Type before calling response.json() and treat non-JSON 200s as network/captive errors.

Example fix

// before
if (!data.access_token) {
  throw new Error('Token exchange response missing access_token')
}

// after
if (!data.access_token) {
  log.error('[OAuth:OpenAI] exchange 2xx but no access_token', JSON.stringify(data))
  throw new ReauthRequiredError('OpenAI token exchange returned no access_token. Restart login.')
}
Defensive patterns

Strategy: type-guard

Type guard

function isOpenAIExchangeResponse(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.login({ openUrl, signal })
} catch (e) {
  if (/missing access_token/i.test(String(e))) {
  // 2xx but malformed — captive portal or contract drift; restart login
  throw new ReauthRequiredError('OpenAI exchange returned no access_token. Restart login.')
  }
  throw e
}

Prevention

When it happens

Trigger: OpenAI changed the exchange response shape (field renamed); a 200 response body that was HTML/empty due to a captive portal or proxy; partial JSON; the account is in a state where tokens are withheld with a 200 + error field instead of a 4xx.

Common situations: Captive portal returning a 200 page; auth.openai.com contract drift; middleware truncating the response; the consent flow returned a 2xx but did not actually grant (rare OpenAI edge).

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/b894c12b0034255f. Report an issue: GitHub.

Appendix: source

Thrown at src/main/oauth/providers/openai.ts:155

      code_verifier: verifier,
      redirect_uri: REDIRECT_URI,
    }),
  })

  if (!response.ok) {
    const text = await response.text()
    log.error('[OAuth:OpenAI] Token exchange failed:', text)
    throw new Error(`Token exchange 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 exchange response missing access_token')
  }

  return {
    accessToken: data.access_token,
    refreshToken: data.refresh_token,
    expiresAt: typeof data.expires_in === 'number' ? Date.now() + data.expires_in * 1000 - 5 * 60 * 1000 : undefined,
    extra: {
      accountId: extractAccountId(data.access_token),
    },
  }
}

View on GitHub (pinned to 81571269ad)