can1357/oh-my-pi · error · AIError.OAuthError
xAI device-code response returned invalid JSON: ${error inst
Error message
xAI device-code response returned invalid JSON: ${error instanceof Error ? error.message : String(error)} What it means
Thrown by requestXAIDeviceAuthorization when response.json() fails — the device-code endpoint returned a 2xx status but a body that is not valid JSON (HTML error page, empty body, truncated response). The parse error is attached as `cause`. A success status with an unparseable body usually indicates an intermediary or a non-JSON response contract, not an xAI application error.
Source
Thrown at packages/ai/src/registry/oauth/xai-oauth.ts:405
if (!response.ok) {
let detail = "";
try {
detail = (await response.text()).trim();
} catch {
// Ignore body-read failures; the status code is the diagnostic.
}
throw new AIError.OAuthError(`xAI device-code request failed: ${response.status}${detail ? ` ${detail}` : ""}`, {
kind: "device-auth",
provider: "xai",
status: response.status,
});
}
let payload: unknown;
try {
payload = await response.json();
} catch (error) {
throw new AIError.OAuthError(
`xAI device-code response returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
{ kind: "validation", provider: "xai", cause: error },
);
}
return parseXAIDeviceAuthorization(payload);
}
async function pollXAIDeviceToken(
tokenEndpoint: string,
deviceCode: string,
fetchImpl: FetchImpl,
signal?: AbortSignal,
): Promise<OAuthDeviceCodePollResult<OAuthCredentials>> {
let response: Response;
try {
const timeoutSignal = AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS);
response = await fetchImpl(tokenEndpoint, {
method: "POST",View on GitHub (pinned to 9690622007)
Solutions
- Look at `error.cause` and curl the device-code endpoint from the same machine to see the raw non-JSON body.
- Log into the network (captive portal) or disable the intercepting proxy/VPN — HTML block pages are the top cause.
- Retry the login after network conditions change; truncated responses are often transient.
- If it reproduces consistently, capture the body and report/update the ai package in case xAI changed its response format.
Example fix
// before: blindly parsing
const body = JSON.parse(await res.text());
// after: guard the parse and surface the raw body for diagnosis
const text = await res.text();
let body: unknown;
try {
body = JSON.parse(text);
} catch (err) {
throw new Error(`xAI device-code endpoint returned non-JSON: ${text.slice(0, 200)}`, { cause: err });
} Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: probe that the device-code URL returns JSON, not an HTML block page
const probe = await fetch(xaiDeviceCodeUrl, { method: "POST", headers: { Accept: "application/json" } });
const ct = probe.headers.get("content-type") ?? "";
if (!ct.includes("json")) {
throw new Error(`xAI device-code endpoint returned non-JSON content-type: ${ct} — captive portal/proxy likely`);
} Try / catch
try {
await xaiProvider.device();
} catch (err) {
if (err instanceof AIError.OAuthError && err.kind === "validation" && err.message.includes("invalid JSON")) {
logger.error("xAI device-code response was not JSON — inspect err.cause and check for proxy/captive portal", { cause: err.cause });
// advise user: log into captive portal or bypass SSL-inspecting proxy
} else {
throw err;
}
} Prevention
- Complete captive-portal sign-in before running device login on public Wi-Fi.
- Bypass or properly configure SSL-inspection proxies for xAI domains.
- Check `err.cause` — the JSON SyntaxError plus the content-type tells you what was actually served.
- Retry once after network changes; truncated bodies are often transient.
When it happens
Trigger: GET/POST to the device-code endpoint returns 200 with HTML (captive portal, proxy block page), an empty body, or truncated/invalid JSON; response.json() throws SyntaxError ('Unexpected token < in JSON', 'Unexpected end of JSON input').
Common situations: Captive Wi-Fi portals intercepting HTTPS; corporate SSL-inspection proxies injecting error pages; CDN edge errors served with 200; DNS hijacking; response body cut off by a flaky connection.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- xAI device-code response was not a JSON object.
- xAI device-code response missing or invalid required fields.
- ${label} was not a JSON object
- ${label} missing access_token
- ${label} missing refresh_token
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/348a1cb432925ce7.
Report an issue: GitHub.