nexu-io/open-design · error
xAI OAuth state mismatch: expected serverId=${XAI_PROVIDER_I
Error message
xAI OAuth state mismatch: expected serverId=${XAI_PROVIDER_ID}, got ${consumed.serverId} What it means
Raised by completeXAIAuth() after a successful consume() when the stored PendingAuthState.serverId does not equal XAI_PROVIDER_ID ('xai'). The PendingAuthCache is shared across providers, so a consumed state must be validated to belong to the xAI flow before exchanging its code — otherwise one provider's code would be exchanged against another provider's token endpoint.
Source
Thrown at apps/daemon/src/integrations/xai-oauth.ts:134
code: string;
fetchImpl?: typeof fetch;
}
/**
* Post-callback half of the OAuth dance. Looks up `state` in `pending`,
* validates it (one-shot, TTL-checked by `PendingAuthCache`), and
* exchanges `code` for tokens. Throws if `state` is unknown, expired,
* already consumed, or was issued for a different provider.
*/
export async function completeXAIAuth(
input: CompleteXAIAuthInput,
): Promise<OAuthTokenResponse> {
const consumed = input.pending.consume(input.state);
if (!consumed) {
throw new Error('xAI OAuth state not found or expired');
}
if (consumed.serverId !== XAI_PROVIDER_ID) {
throw new Error(
`xAI OAuth state mismatch: expected serverId=${XAI_PROVIDER_ID}, got ${consumed.serverId}`,
);
}
return exchangeCodeForToken(
{
tokenEndpoint: consumed.tokenEndpoint,
clientId: consumed.clientId,
redirectUri: consumed.redirectUri,
code: input.code,
codeVerifier: consumed.codeVerifier,
},
input.fetchImpl ?? fetch,
);
}
export interface RefreshXAITokenInput {
refreshToken: string;
fetchImpl?: typeof fetch;View on GitHub (pinned to 5be4028344)
Solutions
- Route the callback by provider: dispatch to the matching complete*Auth based on which authorize flow issued the state, not a fixed xAI path.
- Use provider-scoped redirect ports/paths so cross-provider callbacks cannot collide.
- If seeding PendingAuthState in tests, set serverId: XAI_PROVIDER_ID.
Example fix
// before
if (consumed.serverId !== XAI_PROVIDER_ID) {
throw new Error(`xAI OAuth state mismatch: expected serverId=${XAI_PROVIDER_ID}, got ${consumed.serverId}`);
}
// after (route to the correct provider instead of throwing)
if (consumed.serverId !== XAI_PROVIDER_ID) {
return routeToProviderCallback(consumed.serverId, input);
} Defensive patterns
Strategy: validation
Validate before calling
import { XAI_PROVIDER_ID } from '../integrations/xai-oauth.js';
function stateBelongsToXai(consumed: { serverId: string }): boolean {
return consumed.serverId === XAI_PROVIDER_ID;
}
const consumed = input.pending.peek(input.state); // if your cache exposes a non-consuming peek
if (consumed && !stateBelongsToXai(consumed)) {
return { ok: false, reason: 'wrong_provider' };
} Type guard
function isOAuthStateMismatch(err: unknown): boolean {
return err instanceof Error && err.message.startsWith('xAI OAuth state mismatch:');
} Try / catch
try {
return await completeXAIAuth(input);
} catch (err) {
if (err instanceof Error && err.message.startsWith('xAI OAuth state mismatch:')) {
// route to the provider that actually owns this state
return routeToProviderCallback(extractServerId(err), input);
}
throw err;
} Prevention
- Use provider-scoped redirect ports/paths so cross-provider callbacks cannot collide.
- Dispatch callbacks to the matching provider handler, not a fixed xAI path.
- When seeding PendingAuthState, always set serverId to the issuing provider id.
When it happens
Trigger: A callback for a different OAuth provider (sharing the same PendingAuthCache and state space) lands on the xAI completion path, or a state value collided across providers. The consume succeeded (state was valid) but serverId mismatch proves cross-provider routing.
Common situations: Two providers use the same loopback redirect port and a callback for provider A reaches the xAI handler; state generation produced a duplicate across providers (very unlikely with generateState); test fixtures seeding states with the wrong serverId; refactor that changed a provider id but not stored states.
Related errors
- xAI OAuth state not found or expired
- Unsupported protocol: ${protocol}
- no xAI credentials — sign in with your SuperGrok subscriptio
- invalid JSON in ${filePath}: ${message}
- ${filePath} must contain a JSON object
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/61549b8179029e2b.
Report an issue: GitHub.