{"record":{"id":"f1a3f0209a796f70","repo":"can1357/oh-my-pi","slug":"label-missing-access-token","errorCode":null,"errorMessage":"${label} missing access_token","messagePattern":"(.+?) missing access_token","errorType":"validation","errorClass":"AIError.OAuthError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/registry/oauth/xai-oauth.ts","lineNumber":336,"sourceCode":"\t\tverificationUriComplete,\n\t\texpiresInSeconds,\n\t\tintervalSeconds,\n\t};\n}\n\nfunction parseXAITokenResponse(payload: unknown, label: string, refreshTokenFallback?: string): OAuthCredentials {\n\tif (!isRecord(payload)) {\n\t\tthrow new AIError.OAuthError(`${label} was not a JSON object`, {\n\t\t\tkind: \"validation\",\n\t\t\tprovider: \"xai\",\n\t\t});\n\t}\n\tconst accessToken = typeof payload.access_token === \"string\" ? payload.access_token : \"\";\n\tconst responseRefreshToken = typeof payload.refresh_token === \"string\" ? payload.refresh_token : \"\";\n\tconst refreshToken = responseRefreshToken || refreshTokenFallback || \"\";\n\tconst expiresInSeconds = payload.expires_in;\n\tif (!accessToken) {\n\t\tthrow new AIError.OAuthError(`${label} missing access_token`, {\n\t\t\tkind: \"validation\",\n\t\t\tprovider: \"xai\",\n\t\t});\n\t}\n\tif (!refreshToken) {\n\t\tthrow new AIError.OAuthError(`${label} missing refresh_token`, {\n\t\t\tkind: \"validation\",\n\t\t\tprovider: \"xai\",\n\t\t});\n\t}\n\tif (typeof expiresInSeconds !== \"number\" || !Number.isFinite(expiresInSeconds)) {\n\t\tthrow new AIError.OAuthError(`${label} missing expires_in`, {\n\t\t\tkind: \"validation\",\n\t\t\tprovider: \"xai\",\n\t\t});\n\t}\n\treturn {\n\t\taccess: accessToken,","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/registry/oauth/xai-oauth.ts#L318-L354","documentation":"Thrown by parseXAITokenResponse when the token payload is a JSON object but access_token is missing or not a non-empty string. Without an access token the library cannot construct OAuthCredentials, so the exchange is treated as failed. The message's label tells you which exchange (initial device poll or refresh) produced the bad body.","triggerScenarios":"pollXAIDeviceToken gets HTTP 200 with a body lacking a string access_token (e.g. only an error field, or an empty token), or the credentials refresh path receives a payload without access_token; parseXAITokenResponse throws '<label> missing access_token'.","commonSituations":"xAI returning an OAuth error JSON (error=invalid_grant etc.) with HTTP 200; expired/revoked device code; refresh token revoked server-side; clock or account issues on the xAI side.","solutions":["Read the rest of the payload (curl the token endpoint) — an `error` field usually explains the rejection (invalid_grant, expired_token, slow_down).","Re-run the device login flow (`omp` login) to obtain a fresh device code and tokens.","If refreshing, delete the stored xAI credentials and re-authenticate — the refresh token is likely invalid.","Update the ai package if xAI renamed access_token in a newer token response format."],"exampleFix":"// before: assuming a 200 means success\nif (res.ok) {\n  const body = await res.json();\n  useToken(body.access_token); // may be undefined\n}\n// after: check the OAuth error field before consuming the token\nconst body = await res.json();\nif (body.error) {\n  throw new Error(`token endpoint error: ${body.error}`);\n}\nif (typeof body.access_token !== \"string\" || !body.access_token) {\n  throw new Error(\"token response missing access_token\");\n}\nuseToken(body.access_token);","handlingStrategy":"try-catch","validationCode":"// preflight: reject token bodies that carry an OAuth error or lack access_token\nconst body: Record<string, unknown> = await res.json();\nif (typeof body.error === \"string\") {\n  throw new Error(`xAI token exchange rejected: ${body.error}`);\n}\nif (typeof body.access_token !== \"string\" || body.access_token === \"\") {\n  throw new Error(\"xAI token response lacks access_token\");\n}","typeGuard":"function hasAccessToken(v: unknown): v is { access_token: string } & Record<string, unknown> {\n  return typeof v === \"object\" && v !== null &&\n    typeof (v as Record<string, unknown>).access_token === \"string\" &&\n    (v as { access_token: string }).access_token !== \"\";\n}","tryCatchPattern":"try {\n  await xaiProvider.credentials();\n} catch (err) {\n  if (err instanceof AIError.OAuthError && err.message.includes(\"missing access_token\")) {\n    // stale/revoked grant — clear stored credentials and re-run device login\n    await deleteStoredXAICredentials();\n    return xaiProvider.credentials();\n  }\n  throw err;\n}","preventionTips":["Clear stale xAI credentials before re-authenticating after long inactivity.","Check the token body's `error` field first — most missing access_token cases are invalid_grant/expired_token.","Complete device authorization in the browser before the code expires to avoid denied exchanges.","Monitor xAI status pages during 5xx incidents before blaming stored credentials."],"tags":["oauth","xai","access-token","missing-field","device-flow"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}