upstash/context7 · error · Error
err.error_description || err.error || "Device token poll fai
Error message
err.error_description || err.error || "Device token poll failed"
What it means
`pollDeviceToken` maps RFC 8628 device-flow error codes to poll statuses (authorization_pending, slow_down, access_denied, expired_token) and treats 5xx as transient above. This throw is the default branch: the token endpoint returned a non-ok, non-5xx response whose `error` code the CLI does not recognize, so it throws `error_description || error || "Device token poll failed"`.
Source
Thrown at packages/cli/src/utils/auth.ts:305
const err = (await response.json().catch(() => ({}))) as TokenErrorResponse;
return {
status: "transient",
errorMessage: err.error_description || err.error || `HTTP ${response.status}`,
};
}
const err = (await response.json().catch(() => ({}))) as TokenErrorResponse;
switch (err.error) {
case "authorization_pending":
return { status: "pending" };
case "slow_down":
return { status: "slow_down" };
case "access_denied":
return { status: "denied" };
case "expired_token":
return { status: "expired" };
default:
throw new Error(err.error_description || err.error || "Device token poll failed");
}
}
View on GitHub (pinned to 5284672feb)
Solutions
- Restart the login flow to get a fresh device_code and user_code
- Make sure the whole flow runs against one baseUrl (no mid-flow env changes)
- Update the CLI so it knows the latest error codes
- Check the message body — it carries the server's own error_description
Defensive patterns
Strategy: try-catch
Validate before calling
// Only poll while the device code is still live
const deadline = Date.now() + response.expires_in * 1000;
if (Date.now() >= deadline) throw new Error('device_code already expired — restart login'); Type guard
function isKnownDeviceFlowError(e: string): e is 'authorization_pending' | 'slow_down' | 'access_denied' | 'expired_token' {
return ['authorization_pending', 'slow_down', 'access_denied', 'expired_token'].includes(e);
} Try / catch
try {
const result = await pollDeviceToken(baseUrl, clientId, deviceCode);
if (result.status === 'transient') continue; // keep polling
} catch (error) {
// Unknown error code: terminal — restart the whole device flow, do not retry the poll
throw new Error(`device flow aborted: ${error instanceof Error ? error.message : error}`);
} Prevention
- Keep baseUrl and client_id constant for the entire device flow
- Honor expires_in and stop polling after it passes
- Restart login on any unrecognized poll error rather than looping
When it happens
Trigger: Polling /api/oauth/device/token with a device_code that was issued for a different client_id or baseUrl (`invalid_grant`); `invalid_client` from a stale CLI_CLIENT_ID; the server introducing a new error code the CLI build predates.
Common situations: Device code from a different CLI version or base URL; clock skew or code typed into a different environment; server-side changes to the device flow after an outage.
Related errors
- await describeErrorResponse(response, fallback)
- downloadData.error || "no files"
- describeConnectionError(error, url)
- Failed to write to ${skillPath}: ${error.message}
- Skipped ${mcpPath}: could not parse (${err instanceof Error
AI-assisted analysis of upstash/context7@5284672feb (2026-08-18).
Data as JSON: /api/errors/29afd4eaa1364c05.
Report an issue: GitHub.