can1357/oh-my-pi · error · AIError.OAuthError

xAI device-code response was not a JSON object.

Error message

xAI device-code response was not a JSON object.

What it means

Thrown by parseXAIDeviceAuthorization when the body returned by the xAI device-code endpoint parsed as JSON but is not a plain object (e.g. an array, string, number, or null). The library strictly validates every OAuth payload it receives from xAI before extracting device_code/user_code/verification_uri, and only JSON objects can carry those fields. This is a response-shape validation guard, not a network failure.

Source

Thrown at packages/ai/src/registry/oauth/xai-oauth.ts:282

	return validateXAIBillingEndpoint(url.toString());
}

/**
 * Headers for SuperGrok CLI billing (`cli-chat-proxy.grok.com`).
 * Official Grok CLI also sends `X-XAI-Token-Auth: xai-grok-cli` on this host;
 * include it so billing stays on the same product gate as chat inference.
 */
export function getXAICliBillingHeaders(options: { accessToken: string }): Record<string, string> {
	return {
		Authorization: `Bearer ${options.accessToken}`,
		Accept: "application/json",
		"X-XAI-Token-Auth": "xai-grok-cli",
	};
}

function parseXAIDeviceAuthorization(payload: unknown): XAIDeviceAuthorization {
	if (!isRecord(payload)) {
		throw new AIError.OAuthError("xAI device-code response was not a JSON object.", {
			kind: "validation",
			provider: "xai",
		});
	}

	const deviceCode = typeof payload.device_code === "string" ? payload.device_code.trim() : "";
	const userCode = typeof payload.user_code === "string" ? payload.user_code.trim() : "";
	const verificationUri = typeof payload.verification_uri === "string" ? payload.verification_uri.trim() : "";
	const verificationUriComplete =
		typeof payload.verification_uri_complete === "string" ? payload.verification_uri_complete.trim() : "";
	const expiresInSeconds = payload.expires_in;
	const intervalSeconds = payload.interval;
	if (
		!deviceCode ||
		!userCode ||
		!verificationUri ||
		!verificationUriComplete ||
		typeof expiresInSeconds !== "number" ||

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry the device-code request — a transient proxy or CDN response is the most common cause; run `omp` login again.
  2. Check network middleboxes (corporate proxies, VPN, captive portals) that may rewrite the xAI response body.
  3. If using a custom fetchImpl, verify it returns the raw xAI JSON object and not a wrapped/transformed value.
  4. Inspect the raw response from https://device-code endpoint with curl to confirm what xAI actually returns; update the ai package if the response contract changed.

Example fix

// before: asserting a shape the parser rejects
const payload: any = await res.json();
const list = Array.isArray(payload) ? payload : [payload];
// after: let the library parse; only pass through the raw JSON object
const payload: unknown = await res.json();
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
  throw new Error("xAI device-code endpoint did not return a JSON object");
}
Defensive patterns

Strategy: type-guard

Validate before calling

// preflight: confirm the endpoint answers with a JSON object before invoking the flow
const res = await fetch(xaiDeviceCodeUrl, { method: "POST", headers: { Accept: "application/json" } });
const body: unknown = await res.json();
if (body === null || typeof body !== "object" || Array.isArray(body)) {
  throw new Error("xAI device-code endpoint is not returning a JSON object; check proxy/network");
}

Type guard

function isJsonObject(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}

Try / catch

try {
  await xaiProvider.device();
} catch (err) {
  if (err instanceof AIError.OAuthError && err.kind === "validation" && err.message.includes("not a JSON object")) {
    logger.warn("xAI returned a non-object device-code payload; retrying may help", { cause: err });
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: requestXAIDeviceAuthorization POSTs to XAI_OAUTH_DEVICE_CODE_URL and calls response.json(); if that yields a non-object JSON value (array, string, number, boolean, or null), parseXAIDeviceAuthorization throws immediately via isRecord(payload) === false.

Common situations: xAI endpoint returning a bare JSON array or quoted string; an intercepting proxy/captive portal that answers 200 with a stub JSON document; a mocked or overridden fetchImpl (tests, SDK embedding) returning the wrong shape; xAI changing the device-flow response envelope in an API revision.

Related errors


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