{"record":{"id":"7fbf5ac4557f5ec6","repo":"can1357/oh-my-pi","slug":"xai-device-code-response-was-not-a-json-object","errorCode":null,"errorMessage":"xAI device-code response was not a JSON object.","messagePattern":"xAI device-code response was not a JSON object\\.","errorType":"validation","errorClass":"AIError.OAuthError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/registry/oauth/xai-oauth.ts","lineNumber":282,"sourceCode":"\treturn validateXAIBillingEndpoint(url.toString());\n}\n\n/**\n * Headers for SuperGrok CLI billing (`cli-chat-proxy.grok.com`).\n * Official Grok CLI also sends `X-XAI-Token-Auth: xai-grok-cli` on this host;\n * include it so billing stays on the same product gate as chat inference.\n */\nexport function getXAICliBillingHeaders(options: { accessToken: string }): Record<string, string> {\n\treturn {\n\t\tAuthorization: `Bearer ${options.accessToken}`,\n\t\tAccept: \"application/json\",\n\t\t\"X-XAI-Token-Auth\": \"xai-grok-cli\",\n\t};\n}\n\nfunction parseXAIDeviceAuthorization(payload: unknown): XAIDeviceAuthorization {\n\tif (!isRecord(payload)) {\n\t\tthrow new AIError.OAuthError(\"xAI device-code response was not a JSON object.\", {\n\t\t\tkind: \"validation\",\n\t\t\tprovider: \"xai\",\n\t\t});\n\t}\n\n\tconst deviceCode = typeof payload.device_code === \"string\" ? payload.device_code.trim() : \"\";\n\tconst userCode = typeof payload.user_code === \"string\" ? payload.user_code.trim() : \"\";\n\tconst verificationUri = typeof payload.verification_uri === \"string\" ? payload.verification_uri.trim() : \"\";\n\tconst verificationUriComplete =\n\t\ttypeof payload.verification_uri_complete === \"string\" ? payload.verification_uri_complete.trim() : \"\";\n\tconst expiresInSeconds = payload.expires_in;\n\tconst intervalSeconds = payload.interval;\n\tif (\n\t\t!deviceCode ||\n\t\t!userCode ||\n\t\t!verificationUri ||\n\t\t!verificationUriComplete ||\n\t\ttypeof expiresInSeconds !== \"number\" ||","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/registry/oauth/xai-oauth.ts#L264-L300","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: asserting a shape the parser rejects\nconst payload: any = await res.json();\nconst list = Array.isArray(payload) ? payload : [payload];\n// after: let the library parse; only pass through the raw JSON object\nconst payload: unknown = await res.json();\nif (typeof payload !== \"object\" || payload === null || Array.isArray(payload)) {\n  throw new Error(\"xAI device-code endpoint did not return a JSON object\");\n}","handlingStrategy":"type-guard","validationCode":"// preflight: confirm the endpoint answers with a JSON object before invoking the flow\nconst res = await fetch(xaiDeviceCodeUrl, { method: \"POST\", headers: { Accept: \"application/json\" } });\nconst body: unknown = await res.json();\nif (body === null || typeof body !== \"object\" || Array.isArray(body)) {\n  throw new Error(\"xAI device-code endpoint is not returning a JSON object; check proxy/network\");\n}","typeGuard":"function isJsonObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  await xaiProvider.device();\n} catch (err) {\n  if (err instanceof AIError.OAuthError && err.kind === \"validation\" && err.message.includes(\"not a JSON object\")) {\n    logger.warn(\"xAI returned a non-object device-code payload; retrying may help\", { cause: err });\n  } else {\n    throw err;\n  }\n}","preventionTips":["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."],"tags":["oauth","xai","device-flow","response-validation","json"],"backgroundTag":"oauth-response-shape-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}