{"record":{"id":"0bd8d84e22849f96","repo":"chatboxai/chatbox","slug":"token-refresh-failed-response-status","errorCode":null,"errorMessage":"Token refresh failed: ${response.status}","messagePattern":"Token refresh failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/oauth/providers/openai.ts","lineNumber":105,"sourceCode":"  async refreshToken(credentials) {\n    if (!credentials.refreshToken) {\n      throw new Error('No refresh token available')\n    }\n\n    const response = await fetch(TOKEN_URL, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n      body: new URLSearchParams({\n        grant_type: 'refresh_token',\n        refresh_token: credentials.refreshToken,\n        client_id: CLIENT_ID,\n      }),\n    })\n\n    if (!response.ok) {\n      const text = await response.text()\n      log.error('[OAuth:OpenAI] Token refresh failed:', text)\n      throw new Error(`Token refresh failed: ${response.status}`)\n    }\n\n    const data = (await response.json()) as {\n      access_token?: string\n      refresh_token?: string\n      expires_in?: number\n    }\n\n    if (!data.access_token) {\n      throw new Error('Token refresh response missing access_token')\n    }\n\n    return {\n      accessToken: data.access_token,\n      refreshToken: data.refresh_token || credentials.refreshToken,\n      expiresAt: typeof data.expires_in === 'number' ? Date.now() + data.expires_in * 1000 - 5 * 60 * 1000 : undefined,\n      extra: {\n        accountId: extractAccountId(data.access_token),","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/oauth/providers/openai.ts#L87-L123","documentation":"Thrown when OpenAI's refresh POST to auth.openai.com/oauth/token returns non-2xx. Note this message interpolates only response.status (not the body), even though the body is read into `text` and logged — the thrown error is less informative than the log. Common cause is an invalid/expired/revoked refresh token.","triggerScenarios":"Refresh token revoked by the user at OpenAI; token expired due to inactivity; client_id mismatch; network/5xx failure; concurrent refresh invalidating the prior token.","commonSituations":"User disconnected the app from their OpenAI account; long idle period; a second device refreshed first; CLIENT_ID constant drifted.","solutions":["On 400/401, clear stored credentials and run login() again — the refresh token is dead.","On 5xx or network error, retry once with backoff before surfacing.","Improve the thrown error to include the body text (it is already being read) so callers can branch on the reason.","Serialize refresh calls per account to avoid rotation races."],"exampleFix":"// before\nconst text = await response.text()\nlog.error('[OAuth:OpenAI] Token refresh failed:', text)\nthrow new Error(`Token refresh failed: ${response.status}`)\n\n// after\nconst text = await response.text()\nlog.error('[OAuth:OpenAI] Token refresh failed:', response.status, text)\nif (response.status >= 500) throw new TransientError('OpenAI refresh transient')\nif (response.status === 400 || response.status === 401) {\n  throw new ReauthRequiredError(`OpenAI refresh token invalid: ${text}`)\n}\nthrow new Error(`Token refresh failed (${response.status}): ${text}`)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await provider.refreshToken(credentials)\n} catch (e) {\n  const msg = String(e)\n  if (/Token refresh failed/i.test(msg)) {\n  if (/\\b(5\\d\\d|network|fetch)\\b/i.test(msg)) return await provider.refreshToken(credentials)\n  // 400/401: refresh token invalid — clear and re-login\n  await clearStoredCredentials()\n  throw new ReauthRequiredError(msg)\n  }\n  throw e\n}","preventionTips":["Improve the thrown error to include the response body (already read into `text`) so callers can branch on invalid_grant vs transient.","Serialize refresh calls per account to avoid token-rotation races.","Clear stored credentials on 4xx to avoid retrying a dead refresh token."],"tags":["oauth","network","openai","refresh-token"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}