router-for-me/CLIProxyAPI · error

kimi: failed to parse device code response: %w

Error message

kimi: failed to parse device code response: %w

What it means

A 200 response from the device authorization endpoint contained a body that is not valid JSON matching DeviceCodeResponse. The client expects fields like device_code, user_code, verification_uri, interval, expires_in. An HTML error page, empty body, or changed schema will fail json.Unmarshal.

Source

Thrown at internal/auth/kimi/kimi.go:216

	}
	defer func() {
		if errClose := resp.Body.Close(); errClose != nil {
			log.Errorf("kimi device code: close body error: %v", errClose)
		}
	}()

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("kimi: failed to read device code response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("kimi: device code request failed with status %d: %s", resp.StatusCode, string(bodyBytes))
	}

	var deviceCode DeviceCodeResponse
	if err = json.Unmarshal(bodyBytes, &deviceCode); err != nil {
		return nil, fmt.Errorf("kimi: failed to parse device code response: %w", err)
	}

	return &deviceCode, nil
}

// PollForToken polls the token endpoint until the user authorizes or the device code expires.
func (c *DeviceFlowClient) PollForToken(ctx context.Context, deviceCode *DeviceCodeResponse) (*KimiTokenData, error) {
	if deviceCode == nil {
		return nil, fmt.Errorf("kimi: device code is nil")
	}

	interval := time.Duration(deviceCode.Interval) * time.Second
	if interval < defaultPollInterval {
		interval = defaultPollInterval
	}

	deadline := time.Now().Add(maxPollDuration)
	if deviceCode.ExpiresIn > 0 {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Log or print the raw body bytes before unmarshal to see what the server actually returned (HTML vs JSON vs empty)
  2. Retry once — some WAF challenges are one-time; a fresh request may get the real JSON
  3. Disable interfering proxies for auth.kimi.com or add it to a proxy bypass list
  4. If the JSON shape genuinely changed, update DeviceCodeResponse in internal/auth/kimi/kimi.go or pull the latest CLIProxyAPI release
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check the raw body before unmarshal in forks/tests
if len(bodyBytes) == 0 || bodyBytes[0] != '{' {
    return nil, fmt.Errorf("kimi: device code response is not JSON: %.120s", bodyBytes)
}

Try / catch

if err != nil {
    var syntaxErr *json.SyntaxError
    if errors.As(err, &syntaxErr) {
        // body was not JSON: check proxies / WAF, retry once
    }
    return err
}

Prevention

When it happens

Trigger: auth.kimi.com returns 200 with an HTML interstitial (Cloudflare challenge, consent page), a WAF-modified response, a body truncated by the proxy so JSON is incomplete, or Moonshot renames response fields so the struct no longer matches.

Common situations: Cloudflare/WAF injection on the auth host, transparent proxies rewriting responses, API schema changes after a Kimi update (fix upstream), locale-dependent error pages returned with status 200.

Understand the failure class

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/d758ba87936e97d7. Report an issue: GitHub.