{"record":{"id":"cc0d73b7c5f15e38","repo":"sipeed/picoclaw","slug":"device-code-request-failed-s","errorCode":null,"errorMessage":"device code request failed: %s","messagePattern":"device code request failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/auth/oauth.go","lineNumber":271,"sourceCode":"\t\t\"client_id\": cfg.ClientID,\n\t})\n\n\tresp, err := http.Post(\n\t\tcfg.Issuer+\"/api/accounts/deviceauth/usercode\",\n\t\t\"application/json\",\n\t\tstrings.NewReader(string(reqBody)),\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"requesting device code: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading device code response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"device code request failed: %s\", string(body))\n\t}\n\n\tdeviceResp, err := parseDeviceCodeResponse(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parsing device code response: %w\", err)\n\t}\n\n\tif deviceResp.Interval < 1 {\n\t\tdeviceResp.Interval = 5\n\t}\n\n\treturn &DeviceCodeInfo{\n\t\tDeviceAuthID: deviceResp.DeviceAuthID,\n\t\tUserCode:     deviceResp.UserCode,\n\t\tVerifyURL:    cfg.Issuer + \"/codex/device\",\n\t\tInterval:     deviceResp.Interval,\n\t}, nil\n}","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/auth/oauth.go#L253-L289","documentation":"The device-code endpoint answered a non-200 status (oauth.go:271); the raw body is embedded in the error string. Unlike 357 (no response at all), here the server spoke and refused: 404 when cfg.Issuer does not host the /api/accounts/deviceauth/usercode path, 400/422 for an unknown/invalid client_id, 429 for rate limiting, 5xx for upstream faults.","triggerScenarios":"cfg.Issuer pointed at a standard OAuth issuer whose device path differs (404 HTML body); cfg.ClientID from a different environment (invalid client); hammering the endpoint on every poll loop instead of honoring deviceResp.Interval; provider incidents.","commonSituations":"Copy-paste of Google/Anthropic issuer values into a picoclaw device flow; staging client_id used against production; polling loops that call RequestDeviceCode repeatedly instead of the token-poll endpoint.","solutions":["Read the embedded body — it states the server's reason verbatim","404: fix cfg.Issuer to the host that actually serves /api/accounts/deviceauth/usercode","4xx client errors: verify cfg.ClientID belongs to this issuer environment","429/5xx: back off and retry later; do not loop tightly"],"exampleFix":"# before: issuer/path mismatch returns 404\ncfg.Issuer = \"https://oauth2.googleapis.com\"\n\n# after: issuer that implements the picoclaw device-auth endpoint\ncfg.Issuer = \"https://accounts.pico.ltd\"","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, err := auth.RequestDeviceCode(cfg); err != nil {\n    if strings.Contains(err.Error(), \"device code request failed\") {\n        body := err.Error()\n        switch {\n        case strings.Contains(body, \"404\"): fixIssuer(cfg) // wrong issuer/path\n        case strings.Contains(body, \"400\"), strings.Contains(body, \"422\"): fixClientID(cfg)\n        case strings.Contains(body, \"429\"): return retryWithBackoff\n        default: return err\n        }\n    }\n    return err\n}","preventionTips":["Point cfg.Issuer only at providers implementing the picoclaw device-auth path","Keep cfg.ClientID consistent with the issuer environment","Never call RequestDeviceCode in a tight loop; the flow polls tokens per Interval instead"],"tags":["http","oauth","device-flow","config","status-code"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}