{"record":{"id":"eb45ed0a0c931f39","repo":"router-for-me/CLIProxyAPI","slug":"kimi-device-code-expired","errorCode":null,"errorMessage":"kimi: device code expired","messagePattern":"kimi: device code expired","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/kimi/kimi.go","lineNumber":250,"sourceCode":"\n\tdeadline := time.Now().Add(maxPollDuration)\n\tif deviceCode.ExpiresIn > 0 {\n\t\tcodeDeadline := time.Now().Add(time.Duration(deviceCode.ExpiresIn) * time.Second)\n\t\tif codeDeadline.Before(deadline) {\n\t\t\tdeadline = codeDeadline\n\t\t}\n\t}\n\n\tticker := time.NewTicker(interval)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, fmt.Errorf(\"kimi: context cancelled: %w\", ctx.Err())\n\t\tcase <-ticker.C:\n\t\t\tif time.Now().After(deadline) {\n\t\t\t\treturn nil, fmt.Errorf(\"kimi: device code expired\")\n\t\t\t}\n\n\t\t\ttoken, pollErr, shouldContinue := c.exchangeDeviceCode(ctx, deviceCode.DeviceCode)\n\t\t\tif token != nil {\n\t\t\t\treturn token, nil\n\t\t\t}\n\t\t\tif !shouldContinue {\n\t\t\t\treturn nil, pollErr\n\t\t\t}\n\t\t\t// Continue polling\n\t\t}\n\t}\n}\n\n// exchangeDeviceCode attempts to exchange the device code for an access token.\n// Returns (token, error, shouldContinue).\nfunc (c *DeviceFlowClient) exchangeDeviceCode(ctx context.Context, deviceCode string) (*KimiTokenData, error, bool) {\n\tdata := url.Values{}","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/kimi/kimi.go#L232-L268","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// Surface the deadline to the user up front so they know the window\nfmt.Printf(\"Authorize within %s at %s (code: %s)\\n\",\n    time.Duration(deviceCode.ExpiresIn)*time.Second,\n    deviceCode.VerificationURI, deviceCode.UserCode)","typeGuard":"func isDeviceCodeExpiredErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"kimi: device code expired\")\n}","tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"device code expired\") {\n        // restart flow automatically once\n        deviceCode, _ = k.StartDeviceFlow(ctx)\n        return k.WaitForAuthorization(ctx, deviceCode)\n    }\n    return err\n}","preventionTips":["Print the verification URL prominently and immediately","Authorize promptly after starting login","Keep system clocks NTP-synced"],"tags":["kimi","oauth","device-flow","expiry","timeout"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}