chatboxai/chatbox · error · Error

No refresh token available

Error message

No refresh token available

What it means

Thrown by OpenAI's refreshToken() when the stored credentials contain no refreshToken. OpenAI's flow relies on the offline_access scope to mint a refresh token; if that scope was not requested or the token exchange returned no refresh_token, subsequent refreshes will fail here. Unlike Qwen/MiniMax (which return the existing credentials), the OpenAI provider treats a missing refresh token as a hard error.

Source

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

    const { promise, close } = createCallbackServer(CALLBACK_PORT, signal, CALLBACK_HOST)

    try {
      await openUrl(authUrl.toString())
      const result = await promise

      if (result.state !== state) {
        throw new Error('OAuth state mismatch')
      }

      return await exchangeCodeForTokens(result.code, verifier)
    } finally {
      close()
    }
  },

  async refreshToken(credentials) {
    if (!credentials.refreshToken) {
      throw new Error('No refresh token available')
    }

    const response = await fetch(TOKEN_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        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}`)
    }

View on GitHub (pinned to 81571269ad)

Solutions

  1. Confirm SCOPE includes 'offline_access' before initiating login.
  2. Before calling refreshToken, check credentials.refreshToken; if absent, trigger an interactive login() instead.
  3. Verify exchangeCodeForTokens stores data.refresh_token and that persistence round-trips it.
  4. On this error, clear stored credentials and re-run login() to obtain a fresh refresh-capable token set.

Example fix

// before
const refreshed = await provider.refreshToken(credentials)

// after
if (!credentials.refreshToken) {
  // OpenAI refresh requires offline_access; re-login to obtain it.
  return startInteractiveLogin()
}
const refreshed = await provider.refreshToken(credentials)
Defensive patterns

Strategy: validation

Validate before calling

function hasRefreshToken(c: { refreshToken?: string | null }): boolean {
  return typeof c.refreshToken === 'string' && c.refreshToken.length > 0
}
if (!hasRefreshToken(credentials)) {
  return startInteractiveLogin()
}
await provider.refreshToken(credentials)

Type guard

function hasRefreshToken(c: unknown): c is { refreshToken: string } {
  return typeof c === 'object' && c !== null && typeof (c as any).refreshToken === 'string' && (c as any).refreshToken.length > 0
}

Prevention

When it happens

Trigger: The SCOPE constant is missing 'offline_access'; the token exchange response had no refresh_token field (e.g. consent was not granted for offline access); credentials were persisted from a flow that predated refresh-token support; storage migration dropped the field.

Common situations: Scope string edited to drop offline_access; older build's credentials loaded; user revoked offline consent at OpenAI; manual credential object missing the field in tests.

Related errors


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