router-for-me/CLIProxyAPI · error

kimi: token request failed: %w

Error message

kimi: token request failed: %w

What it means

The HTTP round trip for the device-code-to-token exchange (POST /api/oauth/token) failed at the transport level. This is the per-tick request inside PollForToken; it returns shouldContinue=false, so polling aborts rather than retrying the tick. Causes include DNS failure, connection refused, TLS errors, or the polling context being cancelled mid-request.

Source

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

func (c *DeviceFlowClient) exchangeDeviceCode(ctx context.Context, deviceCode string) (*KimiTokenData, error, bool) {
	data := url.Values{}
	data.Set("client_id", kimiClientID)
	data.Set("device_code", deviceCode)
	data.Set("grant_type", "urn:ietf:params:oauth:grant-type:device_code")

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, kimiTokenURL, strings.NewReader(data.Encode()))
	if err != nil {
		return nil, fmt.Errorf("kimi: failed to create token request: %w", err), false
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Accept", "application/json")
	for k, v := range c.commonHeaders() {
		req.Header.Set(k, v)
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("kimi: token request failed: %w", err), false
	}
	defer func() {
		if errClose := resp.Body.Close(); errClose != nil {
			log.Errorf("kimi token exchange: close body error: %v", errClose)
		}
	}()

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

	// Parse response - Kimi returns 200 for both success and pending states
	var oauthResp struct {
		Error            string  `json:"error"`
		ErrorDescription string  `json:"error_description"`
		AccessToken      string  `json:"access_token"`
		RefreshToken     string  `json:"refresh_token"`

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Verify connectivity to auth.kimi.com (curl https://auth.kimi.com) and retry the login — transient network loss aborts the whole poll
  2. If the machine sleeps during login, keep it awake or re-run login after reconnecting
  3. Note that one failed tick ends the flow by design; do not rely on it self-healing — restart StartDeviceFlow + PollForToken
Defensive patterns

Strategy: retry

Validate before calling

// Keep the network alive during the multi-minute poll
// (e.g. disable sleep, or run poll in a process that stays connected)

Type guard

func isKimiNetErr(err error) bool {
    var netErr net.Error
    return errors.As(err, &netErr)
}

Try / catch

if err != nil {
    var netErr net.Error
    if errors.As(err, &netErr) {
        // one transport hiccup ends the poll by design: restart the flow
        return restartLogin()
    }
    return err
}

Prevention

When it happens

Trigger: DNS resolution failure for auth.kimi.com during the poll, network dropped while waiting for the user to authorize, TLS handshake failure, ctx cancelled so the in-flight request is aborted (surfaces here as a Do error).

Common situations: Laptop sleep/resume during the authorization wait, Wi-Fi switch, container network restart, aggressive DNS timeouts in minimal container images.

Related errors


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