router-for-me/CLIProxyAPI · error
kimi: device code expired
Error message
kimi: device code expired
What it means
The local deadline computed in PollForToken (min of device code ExpiresIn and the 15-minute maxPollDuration) passed on a tick before the token endpoint returned success. The user simply did not authorize in time; the device code is now dead and a new one must be requested.
Source
Thrown at internal/auth/kimi/kimi.go:250
deadline := time.Now().Add(maxPollDuration)
if deviceCode.ExpiresIn > 0 {
codeDeadline := time.Now().Add(time.Duration(deviceCode.ExpiresIn) * time.Second)
if codeDeadline.Before(deadline) {
deadline = codeDeadline
}
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil, fmt.Errorf("kimi: context cancelled: %w", ctx.Err())
case <-ticker.C:
if time.Now().After(deadline) {
return nil, fmt.Errorf("kimi: device code expired")
}
token, pollErr, shouldContinue := c.exchangeDeviceCode(ctx, deviceCode.DeviceCode)
if token != nil {
return token, nil
}
if !shouldContinue {
return nil, pollErr
}
// Continue polling
}
}
}
// exchangeDeviceCode attempts to exchange the device code for an access token.
// Returns (token, error, shouldContinue).
func (c *DeviceFlowClient) exchangeDeviceCode(ctx context.Context, deviceCode string) (*KimiTokenData, error, bool) {
data := url.Values{}View on GitHub (pinned to 78f0c4079e)
Solutions
- Restart the login flow to get a fresh device code, then authorize promptly at the printed verification URL
- Make sure the verification URL and user code are surfaced clearly to the user (the CLI prints them — don't miss them)
- Check container/VM clock sync (NTP) if expiry seems to fire far too early
Defensive patterns
Strategy: fallback
Validate before calling
// Surface the deadline to the user up front so they know the window
fmt.Printf("Authorize within %s at %s (code: %s)\n",
time.Duration(deviceCode.ExpiresIn)*time.Second,
deviceCode.VerificationURI, deviceCode.UserCode) Type guard
func isDeviceCodeExpiredErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "kimi: device code expired")
} Try / catch
if err != nil {
if strings.Contains(err.Error(), "device code expired") {
// restart flow automatically once
deviceCode, _ = k.StartDeviceFlow(ctx)
return k.WaitForAuthorization(ctx, deviceCode)
}
return err
} Prevention
- Print the verification URL prominently and immediately
- Authorize promptly after starting login
- Keep system clocks NTP-synced
When it happens
Trigger: User never visits the verification URI or does not approve within deviceCode.ExpiresIn seconds; the flow started and was left idle past 15 minutes even if ExpiresIn claims longer; system clock skew making time.Now().After(deadline) trigger early.
Common situations: User starts ./cli-proxy-api login, walks away; verification URL buried in terminal output and never opened; slow_down polling backing things up so checks happen late; clock drift in VMs/containers.
Related errors
- kimi: failed to create device code request: %w
- kimi: device code request failed: %w
- kimi: failed to read device code response: %w
- kimi: device code request failed with status %d: %s
- kimi: failed to parse device code response: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/eb45ed0a0c931f39.
Report an issue: GitHub.