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
- 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
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
- 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
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
- err.error_description || err.error || "Device token poll fai
- downloadData.error || "no files"
- describeConnectionError(error, url)
- errorBody.error || errorBody.message || res.statusText
- Failed to write to ${skillPath}: ${error.message}
AI-assisted analysis of upstash/context7@5284672feb (2026-08-18).
Data as JSON: /api/errors/e4f53b6c8c43600b.
Report an issue: GitHub.