musistudio/claude-code-router · error · Error

Kimi CLI OAuth token refresh returned an incomplete token re

Error message

Kimi CLI OAuth token refresh returned an incomplete token response.

What it means

The Kimi OAuth token refresh succeeded at HTTP level but the JSON response was missing access_token, refresh_token, or expires_in (both snake_case and camelCase variants were checked). This means the auth server returned an unexpected payload shape.

Source

Thrown at packages/core/src/agents/local-providers/kimi.ts:432

        "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 = `Kimi CLI OAuth token refresh returned HTTP ${response.status}${tokenRefreshErrorMessage(payload, text)}`;
      if (response.status === 401 || response.status === 403) {
        throw new KimiRefreshAuthError(response.status, message);
      }
      throw new Error(message);
    }
    const accessToken = readString(payload?.access_token) || readString(payload?.accessToken);
    const refreshToken = readString(payload?.refresh_token) || readString(payload?.refreshToken);
    const expiresIn = numberValue(payload?.expires_in) ?? numberValue(payload?.expiresIn);
    if (!accessToken || !refreshToken || !expiresIn) {
      throw new Error("Kimi CLI OAuth token refresh returned an incomplete token response.");
    }
    const refreshed: KimiTokenSet = {
      ...auth,
      accessToken,
      expiresAt: Math.floor(Date.now() / 1000) + expiresIn,
      expiresIn,
      refreshToken,
      scope: readString(payload?.scope) || auth.scope || "",
      tokenType: readString(payload?.token_type) || readString(payload?.tokenType) || "Bearer"
    };
    persistKimiAuth(refreshed);
    return refreshed;
  } catch (error) {
    if (error instanceof Error && error.name === "AbortError") {
      throw new Error(`Kimi CLI OAuth token refresh timed out after ${kimiOauthRefreshTimeoutMs}ms.`);
    }
    throw error;
  } finally {

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Log the raw response text to see what the server actually returned.
  2. Check for corporate proxies/SSL interception mangling responses.
  3. Update to the latest version of the package in case the schema handling changed.
  4. If token shape truly changed, extend readString/numberValue mapping in refreshKimiAuth and re-login.
Defensive patterns

Strategy: try-catch

Type guard

function isIncompleteTokenResponse(e: unknown): boolean {
  return e instanceof Error && e.message.includes('incomplete token response');
}

Try / catch

try { await refreshKimiAuth(auth); } catch (e) { if (isIncompleteTokenResponse(e)) logAuthPayloadForDiagnosis(); throw e; }

Prevention

When it happens

Trigger: refreshKimiAuth parses the response with parseJsonRecord and all of access_token/refresh_token/expires_in are absent or non-string/number. Caused by API changes, an HTML error page parsed as JSON, or a proxy intercepting the response.

Common situations: Moonshot changes response schema, an auth proxy returns an error body with 200, or a partial/truncated response body.

Related errors


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