{"record":{"id":"a01350be4f05c646","repo":"chatboxai/chatbox","slug":"no-refresh-token-available","errorCode":null,"errorMessage":"No refresh token available","messagePattern":"No refresh token available","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/oauth/providers/anthropic.ts","lineNumber":138,"sourceCode":"      throw new Error(`Token exchange failed: ${error}`)\n    }\n\n    const data = (await response.json()) as {\n      access_token: string\n      refresh_token: string\n      expires_in: number\n    }\n\n    return {\n      accessToken: data.access_token,\n      refreshToken: data.refresh_token,\n      expiresAt: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n    }\n  },\n\n  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/json' },\n      body: JSON.stringify({\n        grant_type: 'refresh_token',\n        client_id: CLIENT_ID,\n        refresh_token: credentials.refreshToken,\n      }),\n    })\n\n    if (!response.ok) {\n      const error = await response.text()\n      log.error('[OAuth:Anthropic] Token refresh failed:', error)\n      throw new Error(`Token refresh failed: ${error}`)\n    }\n","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/oauth/providers/anthropic.ts#L120-L156","documentation":"Thrown by AnthropicOAuthProvider.refreshToken() when the stored credentials object has no refreshToken. The provider needs a refresh token to POST grant_type=refresh_token to the token endpoint, so an absent one makes refresh impossible and is treated as a hard failure (unlike Qwen/MiniMax which fall back to returning the existing credentials).","triggerScenarios":"Calling refreshToken on credentials produced by a flow that never stored a refresh_token; credentials loaded from storage where refreshToken was never persisted; a previous exchange returned a body without refresh_token but exchangeCode still resolved.","commonSituations":"Storage migration dropped the refreshToken field; an older build saved credentials before refresh support was added; credentials object was constructed manually for testing without a refreshToken.","solutions":["Before calling refreshToken, check credentials.refreshToken exists; if absent, trigger a fresh interactive login (startLogin + exchangeCode) instead.","Verify the persisted credential schema includes refreshToken and that exchangeCode's return value was stored wholesale.","If you want graceful degradation, treat a missing refreshToken as 're-auth required' and surface a sign-in prompt rather than throwing."],"exampleFix":"// before\nconst refreshed = await provider.refreshToken(credentials)\n\n// after\nif (!credentials.refreshToken) {\n  // Force a fresh interactive login; refresh is impossible without a token.\n  return startInteractiveLogin()\n}\nconst refreshed = await provider.refreshToken(credentials)","handlingStrategy":"validation","validationCode":"function hasRefreshToken(c: { refreshToken?: string | null }): boolean {\n  return typeof c.refreshToken === 'string' && c.refreshToken.length > 0\n}\n\nif (!hasRefreshToken(credentials)) {\n  // refresh impossible — start a fresh interactive login instead\n  return startInteractiveLogin()\n}\nawait provider.refreshToken(credentials)","typeGuard":"function hasRefreshToken(c: unknown): c is { refreshToken: string } {\n  return typeof c === 'object' && c !== null &&\n    typeof (c as any).refreshToken === 'string' && (c as any).refreshToken.length > 0\n}","tryCatchPattern":null,"preventionTips":["Persist the full credentials object returned by exchangeCode, including refreshToken.","Run a storage schema check on startup to confirm refreshToken is present before offering refresh.","Treat a missing refresh token as 're-auth required', not as a recoverable runtime error."],"tags":["oauth","validation","credentials","anthropic","refresh-token"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}