chatboxai/chatbox · error · Error
Failed to refresh OAuth credential for ${providerId}
Error message
Failed to refresh OAuth credential for ${providerId} What it means
Thrown by the desktop OAuth adapter's refreshCredential when the main-process refresh handler returns a result with success: false or missing credentials. The error message prefers the main process's error string, falling back to a generic per-provider message. This is the user-visible failure for token refresh (expired refresh token, revoked grant, network error during refresh).
Source
Thrown at src/renderer/adapters/index.ts:149
const maybeDesktopPlatform = platform as unknown as { ipc?: OAuthIpcInvoker }
if (!maybeDesktopPlatform.ipc) {
throw new Error('OAuth IPC is only available on desktop')
}
return maybeDesktopPlatform.ipc
}
function createDesktopOAuthAdapter(oauthIpc?: OAuthIpcInvoker): OAuthAdapter {
return {
async refreshCredential(providerId: string, credential: OAuthCredentials): Promise<OAuthCredentials> {
const ipc = oauthIpc ?? getDefaultOAuthIpc()
const resultJson = await ipc.invoke(OAuthIpcChannels.REFRESH, providerId, JSON.stringify(credential))
const result = JSON.parse(resultJson) as {
success: boolean
credentials?: OAuthCredentials
error?: string
}
if (!result.success || !result.credentials) {
throw new Error(result.error || `Failed to refresh OAuth credential for ${providerId}`)
}
return result.credentials
},
persistCredential(providerId: string, credential: OAuthCredentials): void {
const settingsProviderId = toOAuthSettingsProviderId(providerId) || providerId
settingsStore.setState((currentSettings) => ({
providers: {
...(currentSettings.providers || {}),
[settingsProviderId]: {
...(currentSettings.providers?.[settingsProviderId] || {}),
oauth: credential,
},
},
}))
},
clearCredential(providerId: string): void {
const settingsProviderId = toOAuthSettingsProviderId(providerId) || providerId
settingsStore.setState((currentSettings) => {View on GitHub (pinned to 81571269ad)
Solutions
- Prompt the user to re-authenticate (start a fresh OAuth flow) since the refresh token is no longer usable.
- Inspect result.error from the main process for invalid_grant or token_expired to decide between re-auth and retry.
- If the failure is transient (network), retry once with backoff before forcing re-auth.
Example fix
// before
if (!result.success || !result.credentials) {
throw new Error(result.error || `Failed to refresh OAuth credential for ${providerId}`)
}
// after — surface a typed error so the UI can distinguish re-auth from retry
if (!result.success || !result.credentials) {
const reason = result.error || 'unknown'
const needsReauth = /invalid_grant|token.*expired|revoked/i.test(reason)
throw new OAuthRefreshError(providerId, reason, { requiresReauth: needsReauth })
} Defensive patterns
Strategy: try-catch
Validate before calling
// Cannot validate server-side refresh outcome client-side; best pre-check is token freshness.
function isLikelyExpired(expiresAt: string | number, skewMs = 60_000): boolean {
const exp = typeof expiresAt === 'string' ? Date.parse(expiresAt) : expiresAt
return Date.now() + skewMs >= exp
} Type guard
function isOAuthRefreshFailure(result: unknown): result is { success: false; error?: string } {
return typeof result === 'object' && result !== null && (result as any).success === false
} Try / catch
try {
await oauthAdapter.refreshCredential(providerId, credential)
} catch (error) {
const msg = error instanceof Error ? error.message : String(error)
if (/invalid_grant|revoked|expired/i.test(msg)) {
await startOAuthFlow(providerId) // re-authenticate
} else {
await backoffRetry(() => oauthAdapter.refreshCredential(providerId, credential))
}
} Prevention
- Refresh tokens proactively before expiry rather than on-demand to avoid hitting the refresh path under load.
- Inspect the main-process error string to distinguish permanent (re-auth) from transient (retry) failures.
- Persist only the refresh_token; never assume a refresh will succeed indefinitely.
When it happens
Trigger: ipc.invoke(OAuthIpcChannels.REFRESH, providerId, credential) returns JSON whose success is false — the provider's token endpoint rejected the refresh_token (expired, revoked, invalid_grant), or the main process could not reach the endpoint. The result object carries an error string that becomes the thrown message.
Common situations: User revoked app access in the provider's account settings; refresh_token expired after long inactivity; the OAuth client secret changed server-side; the provider's token endpoint is temporarily unreachable. On Google/Anthropic providers, invalid_grant is the most common payload.
Related errors
- No refresh token available
- No refresh token available
- OAuth IPC is only available on desktop
- No authorization code found in the input
- Token exchange failed: ${error}
AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12).
Data as JSON: /api/errors/467f6b7f229e3076.
Report an issue: GitHub.