musistudio/claude-code-router · error · Error
Grok CLI OAuth token refresh did not return an access token.
Error message
Grok CLI OAuth token refresh did not return an access token.
What it means
The Grok OAuth token endpoint responded with HTTP 2xx, but the JSON body contained neither access_token nor accessToken as a non-empty string. The library treats this as a malformed token response because it cannot construct a usable GrokTokenSet without an access token.
Source
Thrown at packages/core/src/agents/local-providers/grok.ts:711
}).toString(),
headers: {
"content-type": "application/x-www-form-urlencoded"
},
method: "POST",
signal: controller.signal
});
const text = await response.text();
const payload = parseJsonRecord(text);
if (!response.ok) {
const message = `Grok CLI OAuth token refresh returned HTTP ${response.status}${tokenRefreshErrorMessage(payload, text)}`;
if (response.status === 401 || response.status === 403) {
throw new GrokRefreshAuthError(response.status, message);
}
throw new Error(message);
}
const accessToken = readString(payload?.access_token) || readString(payload?.accessToken);
if (!accessToken) {
throw new Error("Grok CLI OAuth token refresh did not return an access token.");
}
const refreshed: GrokTokenSet = {
...auth,
accessToken,
expiresAt: refreshedGrokExpiresAt(accessToken, payload),
refreshToken: readString(payload?.refresh_token) || readString(payload?.refreshToken) || refreshToken
};
persistRefreshedGrokAuth(refreshed);
return refreshed;
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
throw new Error(`Grok CLI OAuth token refresh timed out after ${timeoutMs}ms.`);
}
throw error;
} finally {
clearTimeout(timer);
}
}View on GitHub (pinned to 99f24806c6)
Solutions
- Log/dump the raw response body to see what the endpoint actually returned
- Re-run login to reset the stored credentials and token endpoint
- If a proxy intercepts HTTPS, bypass it for the OAuth host
- Report/check for a schema change in the Grok CLI OAuth response
Defensive patterns
Strategy: validation
Validate before calling
const res = await fetch(tokenEndpoint, init);
const body = await res.json();
if (!res.ok || !(typeof body.access_token === 'string' && body.access_token)) {
// treat as malformed; re-login rather than retry Try / catch
catch (e) {
if (e instanceof Error && e.message.includes('did not return an access token')) {
await promptRelogin(); // schema/response issue, retrying won't help
}
} Prevention
- Log the raw token response body when debugging OAuth integrations
- Pin the provider CLI version that matches the tested OAuth schema
When it happens
Trigger: resolveGrokAuth triggers refreshGrokAuth; the response is ok but the payload is empty, an HTML error page parsed as an empty record, or a schema change where the token field is renamed or nested differently than access_token/accessToken.
Common situations: A proxy or captive portal returns 200 with an HTML body; the provider A/B tests a new response schema; the discovery document pointed at the wrong endpoint that returns 200 with unrelated JSON.
Related errors
- Grok CLI OAuth token refresh timed out after ${timeoutMs}ms.
- Grok CLI OIDC discovery returned HTTP ${response.status}${to
- Grok CLI OIDC discovery did not return a token endpoint.
- Kimi CLI OAuth token refresh returned HTTP ${response.status
- Grok CLI OIDC discovery timed out after ${timeoutMs}ms.
AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27).
Data as JSON: /api/errors/231ef2ab3215261e.
Report an issue: GitHub.