upstash/context7 · error · Error

await describeErrorResponse(response, fallback)

Error message

await describeErrorResponse(response, fallback)

What it means

`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.

Source

Thrown at packages/cli/src/utils/auth.ts:221

  return `Could not reach ${url}: ${detail}${code ? ` (${code})` : ""}\n${hint}`;
}

async function postForm(url: string, params: URLSearchParams): Promise<Response> {
  try {
    return await fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: params.toString(),
    });
  } catch (error) {
    throw new Error(describeConnectionError(error, url));
  }
}

async function oauthRequest<T>(url: string, params: URLSearchParams, fallback: string): Promise<T> {
  const response = await postForm(url, params);
  if (!response.ok) {
    throw new Error(await describeErrorResponse(response, fallback));
  }
  return (await response.json()) as T;
}

/** RFC 8628 §3.2 default poll interval when the server omits `interval`. */
export const DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;

export async function startDeviceAuthorization(
  baseUrl: string,
  clientId: string
): Promise<DeviceAuthorizationResponse> {
  // Hostname is shown on the server's verification page so the user can confirm
  // that the device they're authorizing matches the one running the CLI
  // (RFC 8628 §5.4 phishing resistance). Best-effort.
  const params = new URLSearchParams({ client_id: clientId });
  try {
    const hostname = os.hostname();
    if (hostname) params.set("hostname", hostname);

View on GitHub (pinned to 5284672feb)

Solutions

  1. Re-run login to mint fresh tokens — invalid/expired refresh tokens are the most common cause
  2. Read the embedded OAuth error code (invalid_grant, invalid_client) in the message to confirm
  3. If the excerpt shows HTML, a proxy/WAF is intercepting — bypass it for the API host
  4. Update the CLI in case OAuth route shapes changed

Example fix

// before: silently swallowing the refresh failure
try { await refreshAccessToken(t); } catch { /* logout */ }

// after: surface describeErrorResponse's detail to the user
try { await refreshAccessToken(t); } catch (e) { console.error(e instanceof Error ? e.message : e); }
Defensive patterns

Strategy: try-catch

Type guard

function isOAuthErrorObject(body: unknown): body is { error: string; error_description?: string } {
  return typeof body === 'object' && body !== null && typeof (body as any).error === 'string';
}

Try / catch

try {
  await oauthRequest(url, params, 'fallback message');
} catch (error) {
  const message = error instanceof Error ? error.message : String(error);
  if (/invalid_grant/i.test(message)) {
    // refresh token dead: clear credentials and re-login rather than retrying
    await clearTokens();
  }
  throw error;
}

Prevention

When it happens

Trigger: 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.

Common situations: Stale credentials file after server-side token revocation; CLI version pointing at changed OAuth routes; WAF rewriting POST bodies; auth server briefly degraded.

Related errors


AI-assisted analysis of upstash/context7@5284672feb (2026-08-18). Data as JSON: /api/errors/e4f53b6c8c43600b. Report an issue: GitHub.