{"record":{"id":"385738263801f9b7","repo":"router-for-me/CLIProxyAPI","slug":"kimi-empty-access-token-in-response","errorCode":null,"errorMessage":"kimi: empty access token in response","messagePattern":"kimi: empty access token in response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/kimi/kimi.go","lineNumber":329,"sourceCode":"\t}\n\n\tif oauthResp.Error != \"\" {\n\t\tswitch oauthResp.Error {\n\t\tcase \"authorization_pending\":\n\t\t\treturn nil, nil, true // Continue polling\n\t\tcase \"slow_down\":\n\t\t\treturn nil, nil, true // Continue polling (with increased interval handled by caller)\n\t\tcase \"expired_token\":\n\t\t\treturn nil, fmt.Errorf(\"kimi: device code expired\"), false\n\t\tcase \"access_denied\":\n\t\t\treturn nil, fmt.Errorf(\"kimi: access denied by user\"), false\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"kimi: OAuth error: %s - %s\", oauthResp.Error, oauthResp.ErrorDescription), false\n\t\t}\n\t}\n\n\tif oauthResp.AccessToken == \"\" {\n\t\treturn nil, fmt.Errorf(\"kimi: empty access token in response\"), false\n\t}\n\n\tvar expiresAt int64\n\tif oauthResp.ExpiresIn > 0 {\n\t\texpiresAt = time.Now().Unix() + int64(oauthResp.ExpiresIn)\n\t}\n\n\treturn &KimiTokenData{\n\t\tAccessToken:  oauthResp.AccessToken,\n\t\tRefreshToken: oauthResp.RefreshToken,\n\t\tTokenType:    oauthResp.TokenType,\n\t\tExpiresAt:    expiresAt,\n\t\tScope:        oauthResp.Scope,\n\t}, nil, false\n}\n\n// RefreshToken exchanges a refresh token for a new access token.\nfunc (c *DeviceFlowClient) RefreshToken(ctx context.Context, refreshToken string) (*KimiTokenData, error) {","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/kimi/kimi.go#L311-L347","documentation":"The token endpoint returned HTTP 200 with no OAuth error field, but the access_token field is empty. The server claimed success yet delivered no usable credential, so the flow treats it as a protocol violation rather than success.","triggerScenarios":"Moonshot-side change where success responses use a different field name (e.g. token moved to data.access_token), a partial response, or an A/B server behavior returning 200 with an empty payload.","commonSituations":"Upstream API contract changes after a Kimi release (parsing struct in kimi.go needs updating), rare server glitches, response mangled by intermediaries.","solutions":["Retry the login once — empty-payload 200s are usually transient server glitches","If persistent, update CLIProxyAPI: this typically means Kimi changed the token response schema and the struct in kimi.go:299 must be adapted","Capture the raw response body (log bodyBytes) to see which field actually holds the token"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"empty access token\") {\n    // transient server glitch usually: retry login once; if persistent, schema changed upstream\n}","preventionTips":["Retry once before escalating","Log raw bodies to detect schema drift","Track Kimi release notes for token endpoint changes"],"tags":["kimi","oauth","device-flow","schema","empty-token"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}