upstash/context7 · error · Error

${fallback} (HTTP ${response.status} from ${response.url}):

Error message

${fallback} (HTTP ${response.status} from ${response.url}): ${excerpt}

What it means

Thrown by oauthRequest() when postForm() succeeds but response.ok is false. describeErrorResponse() first tries to parse the body as an OAuth TokenErrorResponse (error/error_description); if that fails (e.g. HTML from an intercepting proxy) it falls back to '<fallback> (HTTP <status> from <url>): <200-char excerpt>'. The fallback string is supplied per call site (e.g. 'Failed to start device authorization').

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 ca15df0443)

Solutions

  1. Read the excerpt in the message — if it is HTML, the request is hitting a proxy/gateway, not the auth server; fix the base URL.
  2. Confirm the OAuth client_id the CLI is using is still registered and not expired.
  3. Reproduce with curl -i against the same URL to see the full status line and body.
  4. If the body is a real OAuth error (invalid_grant etc.), address that specific error code rather than retrying blindly.

Example fix

// before — fallback message only says "Failed to start device authorization"
// after — log response.status + raw body excerpt before throwing for easier triage
if (!response.ok) {
  const detail = await describeErrorResponse(response, fallback);
  console.error(`oauthRequest ${response.url} -> HTTP ${response.status}`);
  throw new Error(detail);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Nothing to validate client-side beyond a well-formed URL + client_id;
// surface that the request will go out.
function isValidAuthUrl(u: string): boolean {
  try {
    const parsed = new URL(u);
    return parsed.protocol === "https:" && Boolean(parsed.hostname);
  } catch {
    return false;
  }
}
if (!isValidAuthUrl(baseUrl)) {
  throw new Error(`Invalid auth base URL: ${baseUrl}`);
}

Type guard

// Detect an HTML (non-OAuth) response body so the caller can tell proxy noise apart.
function looksLikeHtmlExcerpt(msg: string): boolean {
  return /\b(?:<html|<!doctype|<body)/i.test(msg);
}

Try / catch

try {
  await oauthRequest(url, params, fallback);
} catch (e) {
  const msg = e instanceof Error ? e.message : String(e);
  if (looksLikeHtmlExcerpt(msg)) {
    throw new Error(`${fallback}: request hit a proxy/gateway HTML page, not the auth server. Check the base URL.`);
  }
  throw e; // otherwise it's a real OAuth error body — rethrow verbatim
}

Prevention

When it happens

Trigger: OAuth device-code/token endpoint returns 4xx with an OAuth error body (invalid_client, invalid_request, invalid_grant); an intermediary (proxy/gateway) returns its own HTML error page; the auth host returns a generic 500/502 JSON; the request reached the wrong virtual host.

Common situations: Wrong/migrated client_id; auth server behind a gateway that returns HTML for errors; base URL override pointing at the wrong service; misconfigured load balancer returning 502 with a non-JSON body; rate-limited by an edge proxy.

Related errors


AI-assisted analysis of upstash/context7@ca15df0443 (2026-08-12). Data as JSON: /api/errors/6dc4d90722ba7951. Report an issue: GitHub.