router-for-me/CLIProxyAPI · error

kimi: failed to create device code request: %w

Error message

kimi: failed to create device code request: %w

What it means

http.NewRequestWithContext failed building the RFC 8628 device-authorization POST to https://auth.kimi.com/api/oauth/device_authorization. Method and URL are hardcoded constants, so failure effectively means a nil or already-canceled context was passed into RequestDeviceCode. It fails before any network activity, unlike 219.

Source

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

// commonHeaders returns headers required for Kimi API requests.
func (c *DeviceFlowClient) commonHeaders() map[string]string {
	return map[string]string{
		"X-Msh-Platform":     "CLIProxyAPI",
		"X-Msh-Version":      buildinfo.Version,
		"X-Msh-Device-Name":  getHostname(),
		"X-Msh-Device-Model": getDeviceModel(),
		"X-Msh-Device-Id":    c.deviceID,
	}
}

// RequestDeviceCode initiates the device flow by requesting a device code from Kimi.
func (c *DeviceFlowClient) RequestDeviceCode(ctx context.Context) (*DeviceCodeResponse, error) {
	data := url.Values{}
	data.Set("client_id", kimiClientID)

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, kimiDeviceCodeURL, strings.NewReader(data.Encode()))
	if err != nil {
		return nil, fmt.Errorf("kimi: failed to create device code request: %w", err)
	}
	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: device code request failed: %w", err)
	}
	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)

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Check errors.Is(err, context.Canceled) on the result to confirm prior cancellation.
  2. Use a fresh context with a timeout sized for the whole device flow (user approval can take minutes).
  3. When embedding, ensure the device-flow context is independent of any single HTTP request lifetime.

Example fix

// before
tok, err := c.RequestDeviceCode(shortReqCtx)

// after
flowCtx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
tok, err := c.RequestDeviceCode(flowCtx)
Defensive patterns

Strategy: validation

Validate before calling

if ctx == nil || ctx.Err() != nil {
    return errors.New("device flow context already dead; pass a fresh context")
}
dc, err := c.RequestDeviceCode(ctx)

Try / catch

dc, err := c.RequestDeviceCode(ctx)
if err != nil {
    if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
        // rebuild context; device flow needs minutes, not seconds
    }
}

Prevention

When it happens

Trigger: Kimi login initiated with a context canceled by an early timeout or shutdown; embedding code passing a finished request context into the device flow; (theoretical) corrupted kimiDeviceCodeURL constant.

Common situations: Wrapper scripts with very short auth timeouts canceling before the first POST; SDK integrations reusing a stale context; not seen in normal CLI usage.

Related errors


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