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

  1. Look at `error.cause` and curl the device-code endpoint from the same machine to see the raw non-JSON body.
  2. Log into the network (captive portal) or disable the intercepting proxy/VPN — HTML block pages are the top cause.
  3. Retry the login after network conditions change; truncated responses are often transient.
  4. 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

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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/348a1cb432925ce7. Report an issue: GitHub.