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

  1. Log/dump the raw response body to see what the endpoint actually returned
  2. Re-run login to reset the stored credentials and token endpoint
  3. If a proxy intercepts HTTPS, bypass it for the OAuth host
  4. 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

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


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/231ef2ab3215261e. Report an issue: GitHub.