can1357/oh-my-pi · error · AIError.OAuthError
${label} was not a JSON object
Error message
${label} was not a JSON object What it means
Thrown by parseXAITokenResponse when a token payload (device-flow poll success or refresh via credentials) parses as JSON but is not a plain object. The label parameter identifies which exchange failed (e.g. 'xAI device-code token response'). The library requires an object carrying access_token/refresh_token/expires_in before it can build OAuthCredentials.
Source
Thrown at packages/ai/src/registry/oauth/xai-oauth.ts:326
kind: "validation",
provider: "xai",
});
}
validateXAIEndpoint(verificationUri, "verification_uri");
validateXAIEndpoint(verificationUriComplete, "verification_uri_complete");
return {
deviceCode,
userCode,
verificationUriComplete,
expiresInSeconds,
intervalSeconds,
};
}
function parseXAITokenResponse(payload: unknown, label: string, refreshTokenFallback?: string): OAuthCredentials {
if (!isRecord(payload)) {
throw new AIError.OAuthError(`${label} was not a JSON object`, {
kind: "validation",
provider: "xai",
});
}
const accessToken = typeof payload.access_token === "string" ? payload.access_token : "";
const responseRefreshToken = typeof payload.refresh_token === "string" ? payload.refresh_token : "";
const refreshToken = responseRefreshToken || refreshTokenFallback || "";
const expiresInSeconds = payload.expires_in;
if (!accessToken) {
throw new AIError.OAuthError(`${label} missing access_token`, {
kind: "validation",
provider: "xai",
});
}
if (!refreshToken) {
throw new AIError.OAuthError(`${label} missing refresh_token`, {
kind: "validation",
provider: "xai",View on GitHub (pinned to 9690622007)
Solutions
- Retry the token exchange or re-run device login — transient proxy responses are the usual culprit.
- Check middleboxes (corporate proxy, VPN) that may rewrite the token endpoint body.
- If using a custom fetchImpl, ensure it returns the parsed JSON value as-is without transformation.
- Compare the raw token-endpoint response with curl; if xAI changed the format, update the ai package.
Example fix
// before: assuming the token body is an object
const tokens = await res.json();
saveCredentials(tokens.access_token);
// after: narrow before use
const tokens: unknown = await res.json();
if (tokens === null || typeof tokens !== "object" || Array.isArray(tokens)) {
throw new Error("token endpoint did not return a JSON object");
}
saveCredentials((tokens as Record<string, unknown>).access_token); Defensive patterns
Strategy: type-guard
Validate before calling
// preflight on the token endpoint: confirm object-shaped JSON before an exchange
const probe = await fetch(tokenEndpoint, { method: "POST", headers: { Accept: "application/json" } });
const body: unknown = await probe.json();
if (body === null || typeof body !== "object" || Array.isArray(body)) {
throw new Error("xAI token endpoint returns non-object JSON in this environment; check proxy");
} 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.credentials();
} catch (err) {
if (err instanceof AIError.OAuthError && err.kind === "validation" && err.message.includes("was not a JSON object")) {
logger.warn("xAI token exchange returned a non-object payload", {});
} else {
throw err;
}
} Prevention
- Do not wrap/transform token responses in custom fetchImpl implementations.
- Watch for token-endpoint formats like JWE strings that parse as non-object JSON.
- Test the login flow once per network environment (home, office, VPN) — proxies are the usual culprit.
- Keep @oh-my-pi/pi-ai updated against xAI token-endpoint changes.
When it happens
Trigger: pollXAIDeviceToken receives an HTTP 200 whose JSON body is not an object (array/string/number/null), or the credentials refresh path gets a non-object payload back from the token endpoint; parseXAITokenResponse throws with the exchange's label in the message.
Common situations: Token endpoint returning a JWE string or other non-object token format; proxy mangling the body; custom fetchImpl double-wrapping responses; xAI changing token response encoding.
Related errors
- xAI device-code response was not a JSON object.
- xAI device-code response missing or invalid required fields.
- ${label} missing expires_in
- xAI device-code response returned invalid JSON: ${error inst
- ${label} missing access_token
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/89cb48ee3f2accc6.
Report an issue: GitHub.