{"record":{"id":"e4f53b6c8c43600b","repo":"upstash/context7","slug":"await-describeerrorresponse-response-fallback","errorCode":null,"errorMessage":"await describeErrorResponse(response, fallback)","messagePattern":"await describeErrorResponse\\(response, fallback\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/utils/auth.ts","lineNumber":221,"sourceCode":"  return `Could not reach ${url}: ${detail}${code ? ` (${code})` : \"\"}\\n${hint}`;\n}\n\nasync function postForm(url: string, params: URLSearchParams): Promise<Response> {\n  try {\n    return await fetch(url, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n      body: params.toString(),\n    });\n  } catch (error) {\n    throw new Error(describeConnectionError(error, url));\n  }\n}\n\nasync function oauthRequest<T>(url: string, params: URLSearchParams, fallback: string): Promise<T> {\n  const response = await postForm(url, params);\n  if (!response.ok) {\n    throw new Error(await describeErrorResponse(response, fallback));\n  }\n  return (await response.json()) as T;\n}\n\n/** RFC 8628 §3.2 default poll interval when the server omits `interval`. */\nexport const DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;\n\nexport async function startDeviceAuthorization(\n  baseUrl: string,\n  clientId: string\n): Promise<DeviceAuthorizationResponse> {\n  // Hostname is shown on the server's verification page so the user can confirm\n  // that the device they're authorizing matches the one running the CLI\n  // (RFC 8628 §5.4 phishing resistance). Best-effort.\n  const params = new URLSearchParams({ client_id: clientId });\n  try {\n    const hostname = os.hostname();\n    if (hostname) params.set(\"hostname\", hostname);","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/upstash/context7/blob/5284672feb575908efead6fcf1b5e542f8d607bb/packages/cli/src/utils/auth.ts#L203-L239","documentation":"`oauthRequest` throws when an OAuth endpoint (device code, token, refresh) answers with a non-2xx status. `describeErrorResponse` parses the body for an RFC 6749 `error_description`/`error` object; if the body is not JSON (e.g. an HTML interceptor page) it falls back to `HTTP {status} from {url}` plus a 200-character excerpt of the body.","triggerScenarios":"Refreshing with an expired/revoked refresh_token (`invalid_grant` on /api/oauth/token); wrong client_id; 429 rate limit; a reverse proxy returning an HTML 502 page instead of an OAuth error object.","commonSituations":"Stale credentials file after server-side token revocation; CLI version pointing at changed OAuth routes; WAF rewriting POST bodies; auth server briefly degraded.","solutions":["Re-run login to mint fresh tokens — invalid/expired refresh tokens are the most common cause","Read the embedded OAuth error code (invalid_grant, invalid_client) in the message to confirm","If the excerpt shows HTML, a proxy/WAF is intercepting — bypass it for the API host","Update the CLI in case OAuth route shapes changed"],"exampleFix":"// before: silently swallowing the refresh failure\ntry { await refreshAccessToken(t); } catch { /* logout */ }\n\n// after: surface describeErrorResponse's detail to the user\ntry { await refreshAccessToken(t); } catch (e) { console.error(e instanceof Error ? e.message : e); }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isOAuthErrorObject(body: unknown): body is { error: string; error_description?: string } {\n  return typeof body === 'object' && body !== null && typeof (body as any).error === 'string';\n}","tryCatchPattern":"try {\n  await oauthRequest(url, params, 'fallback message');\n} catch (error) {\n  const message = error instanceof Error ? error.message : String(error);\n  if (/invalid_grant/i.test(message)) {\n    // refresh token dead: clear credentials and re-login rather than retrying\n    await clearTokens();\n  }\n  throw error;\n}","preventionTips":["Refresh tokens proactively before expiry instead of waiting for invalid_grant","Never let a proxy/WAF rewrite OAuth POST bodies — bypass it for the API host","Keep the CLI updated so OAuth route shapes stay in sync"],"tags":["oauth","http","refresh-token","cli"],"backgroundTag":"oauth-token-request-failed","analyzedSha":"5284672feb575908efead6fcf1b5e542f8d607bb","analyzedAt":"2026-08-18T18:00:18.510Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}