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
- Retry the device-code request — a transient proxy or CDN response is the most common cause; run `omp` login again.
- Check network middleboxes (corporate proxies, VPN, captive portals) that may rewrite the xAI response body.
- If using a custom fetchImpl, verify it returns the raw xAI JSON object and not a wrapped/transformed value.
- 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
- Ensure no proxy/VPN rewrites responses from xAI endpoints before running device login.
- When injecting a custom fetchImpl (tests, SDK embedding), return the raw parsed JSON body untouched.
- Keep the ai package current so response-shape assumptions track xAI's live contract.
- Prefer wired networks or stable connections where middleboxes do not inject stub responses.
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
- xAI device-code response missing or invalid required fields.
- ${label} was not a JSON object
- xAI device-code response returned invalid JSON: ${error inst
- ${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/7fbf5ac4557f5ec6.
Report an issue: GitHub.