{"record":{"id":"aa948e299761c0ff","repo":"sipeed/picoclaw","slug":"failed-to-read-request-body-aa948e","errorCode":null,"errorMessage":"failed to read request body","messagePattern":"failed to read request body","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"web/backend/api/oauth.go","lineNumber":176,"sourceCode":"\t\t\t\titem.Status = \"needs_refresh\"\n\t\t\tdefault:\n\t\t\t\titem.Status = \"connected\"\n\t\t\t}\n\t\t}\n\n\t\tprovidersResp = append(providersResp, item)\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t_ = json.NewEncoder(w).Encode(map[string]any{\n\t\t\"providers\": providersResp,\n\t})\n}\n\nfunc (h *Handler) handleOAuthLogin(w http.ResponseWriter, r *http.Request) {\n\tbody, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))\n\tif err != nil {\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tdefer r.Body.Close()\n\n\tvar req struct {\n\t\tProvider string `json:\"provider\"`\n\t\tMethod   string `json:\"method\"`\n\t\tToken    string `json:\"token\"`\n\t}\n\tif err = json.Unmarshal(body, &req); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"invalid JSON: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tprovider, err := normalizeOAuthProvider(req.Provider)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/oauth.go#L158-L194","documentation":"Returned by POST /api/oauth/login (handleOAuthLogin) when io.ReadAll of the request body fails before parsing. The read is capped at 1 MiB; failure is transport-level (connection aborted mid-body, truncated chunked encoding), not payload content — bad JSON would produce 'invalid JSON: %v' instead.","triggerScenarios":"Login POST aborted by the client after headers were sent; proxy cutting the body; Content-Length mismatch on a hand-rolled HTTP client.","commonSituations":"Login dialog unmounted mid-submit (SPA route change); aggressive proxy timeouts; curl interrupted mid-request; browser offline the moment login is clicked.","solutions":["Retry the login POST — transient aborts dominate","Prevent double-submit/abort races in the UI (disable the button while in flight)","Check the reverse proxy's request-body timeout if it recurs"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function loginBody(provider: string, method: string, token?: string): string {\n  if (!provider || !method) throw new Error('provider and method are required');\n  return JSON.stringify({ provider, method, ...(token ? { token } : {}) });\n}","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch('/api/oauth/login', {...});\n  if (res.status === 400 && (await res.text()).includes('read request body')) {\n    /* aborted transfer — retry once with the same payload */\n  }\n} catch (e) { /* network */ }","preventionTips":["Disable the login button while a login POST is in flight to avoid abort/double-submit","Don't unmount the login form mid-request (guard route changes)","Keep the login payload tiny — three string fields"],"tags":["http","request-body","network","oauth"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}