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
- Confirm SCOPE includes 'offline_access' before initiating login.
- Before calling refreshToken, check credentials.refreshToken; if absent, trigger an interactive login() instead.
- Verify exchangeCodeForTokens stores data.refresh_token and that persistence round-trips it.
- 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
- Confirm SCOPE includes 'offline_access' before initiating login.
- Persist data.refresh_token from exchangeCodeForTokens wholesale into storage.
- On a missing refresh token, trigger interactive login() rather than throwing at refresh time.
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
- No refresh token available
- Token refresh response missing access_token
- Token refresh failed: ${response.status}
- Token exchange response missing access_token
- No authorization code found in the input
AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12).
Data as JSON: /api/errors/9dd23212dec5dd3a.
Report an issue: GitHub.