{"record":{"id":"980b5d28bf24ad4e","repo":"decolua/9router","slug":"failed-to-refresh-credentials-please-re-authorize","errorCode":null,"errorMessage":"Failed to refresh credentials. Please re-authorize the connection.","messagePattern":"Failed to refresh credentials\\. Please re-authorize the connection\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/app/api/usage/[connectionId]/route.js","lineNumber":55,"sourceCode":"    copilotTokenExpiresAt: connection.providerSpecificData?.copilotTokenExpiresAt,\n  };\n\n  // Check if refresh is needed (skip when force=true)\n  const needsRefresh = force || executor.needsRefresh(credentials);\n\n  if (!needsRefresh) {\n    return { connection, refreshed: false };\n  }\n\n  // Use executor's refreshCredentials method (with optional proxy)\n  const refreshResult = await executor.refreshCredentials(credentials, console, proxyOptions);\n\n  if (!refreshResult) {\n    // Refresh failed but we still have an accessToken — try with existing token\n    if (connection.accessToken) {\n      return { connection, refreshed: false };\n    }\n    throw new Error(\"Failed to refresh credentials. Please re-authorize the connection.\");\n  }\n\n  // Build update object\n  const now = new Date().toISOString();\n  const updateData = {\n    updatedAt: now,\n  };\n\n  // Update accessToken if present\n  if (refreshResult.accessToken) {\n    updateData.accessToken = refreshResult.accessToken;\n  }\n\n  // Update refreshToken if present\n  if (refreshResult.refreshToken) {\n    updateData.refreshToken = refreshResult.refreshToken;\n  }\n","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/app/api/usage/[connectionId]/route.js#L37-L73","documentation":"refreshAndUpdateCredentials refreshes an OAuth connection's tokens via the provider executor's refreshCredentials. If the refresh returns falsy (failed) AND the connection has no accessToken to fall back on, it throws this error telling the user the connection must be re-authorized. Note: if refresh fails but an accessToken exists, it degrades gracefully and returns refreshed:false instead of throwing.","triggerScenarios":"GET (or POST) /api/usage/<connectionId> runs on a connection whose executor.needsRefresh() says a refresh is due (or force=true), the executor's refreshCredentials fails (invalid/expired refresh_token, revoked grant, provider OAuth endpoint error, network/proxy failure) and connection.accessToken is empty/undefined.","commonSituations":"Provider revoked or rotated the refresh token (Google/Anthropic/OpenAI revoke stale or unused refresh tokens), user revoked app access in provider account settings, refresh token expired after provider-imposed lifetime (e.g. 7-180 days), or the stored connection was created without a refreshToken/accessToken (API-key-only credentials passed to an OAuth executor).","solutions":["Re-authorize the connection: delete/re-create it through the dashboard's provider connection flow so fresh accessToken/refreshToken are stored.","Verify the connection actually has a refreshToken stored; if it was saved without one (e.g. API-key or PKCE-only flow), refresh can never succeed — re-auth is mandatory.","Check provider-side app authorization (revoke lists, security page) and re-consent; for GitHub Copilot also confirm device-flow token is still valid.","If refreshes fail only behind a proxy, verify resolveConnectionProxyConfig output — a broken proxy can make the token endpoint unreachable, causing refreshResult to be falsy."],"exampleFix":"// before\nif (!refreshResult) {\n  if (connection.accessToken) return { connection, refreshed: false };\n  throw new Error(\"Failed to refresh credentials. Please re-authorize the connection.\");\n}\n// after\nif (!refreshResult) {\n  if (connection.accessToken) return { connection, refreshed: false };\n  const err = new Error(\"Failed to refresh credentials. Please re-authorize the connection.\");\n  err.code = \"OAUTH_REFRESH_FAILED\";\n  err.connectionId = connection.id;\n  err.provider = connection.provider;\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":"// Before calling the usage route, check the connection can possibly refresh\nfunction canRefreshOrUse(connection) {\n  return Boolean(connection.accessToken || connection.refreshToken);\n}\nif (!canRefreshOrUse(connection)) throw new Error(\"Connection has no tokens — re-authorize first\");","typeGuard":"function hasUsableCredentials(conn) {\n  return typeof conn?.accessToken === \"string\" && conn.accessToken.length > 0;\n}","tryCatchPattern":"try {\n  const { connection, refreshed } = await refreshAndUpdateCredentials(conn, true);\n} catch (err) {\n  if (err.message.includes(\"re-authorize\")) {\n    // Mark connection as needs-reauth in UI and start the provider OAuth flow again\n  } else {\n    throw err;\n  }\n}","preventionTips":["Refresh tokens proactively (needsRefresh check) instead of forcing at request time","Surface a 'Re-authorize' button whenever refresh fails and no fallback token exists","Log provider refresh errors (HTTP status/body) to distinguish revoked vs transient failures","Never store OAuth connections without a refreshToken when the provider supports rotation"],"tags":["oauth","token-refresh","authentication"],"backgroundTag":"oauth-refresh-token-invalid","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}