{"record":{"id":"31c2fa20835cd218","repo":"sipeed/picoclaw","slug":"failed-to-read-request-body-31c2fa","errorCode":null,"errorMessage":"Failed to read request body","messagePattern":"Failed to read request body","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"web/backend/api/models.go","lineNumber":311,"sourceCode":"\t\t})\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(map[string]any{\n\t\t\"models\":           models,\n\t\t\"total\":            len(models),\n\t\t\"default_model\":    defaultModel,\n\t\t\"provider_options\": modelProviderOptionsForResponse(),\n\t})\n}\n\n// handleAddModel appends a new model configuration entry.\n//\n//\tPOST /api/models\nfunc (h *Handler) handleAddModel(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\ttype custom struct {\n\t\tconfig.ModelConfig\n\t\tAPIKey string `json:\"api_key\"`\n\t}\n\n\tvar mc custom\n\tif err = json.Unmarshal(body, &mc); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Invalid JSON: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tnormalizeIncomingModelConfig(&mc.ModelConfig)\n\n\tif err = validateIncomingModelConfig(&mc.ModelConfig, nil); err != nil {","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/models.go#L293-L329","documentation":"HTTP 400 returned by POST /api/models (handleAddModel) when io.ReadAll(io.LimitReader(r.Body, 1<<20)) returns an error. This is a transport-level failure while streaming the request body, not a payload problem: connection reset mid-upload, invalid chunked transfer encoding, or a client abort. Bodies larger than 1 MiB are silently truncated by LimitReader (surfacing later as 'Invalid JSON'), not rejected here.","triggerScenarios":"POST /api/models where the TCP connection drops partway through the upload, a proxy (nginx/cloudflare) terminates a slow upload, the client uses malformed chunked encoding, or curl is interrupted mid-request.","commonSituations":"Flaky networks on large model payloads (big extra_body blobs), aggressive proxy timeouts, browser tab closed during submit, or a middleware/proxy in front of the backend mangling transfer-encoding.","solutions":["Retry the request from the client; transient connection resets are the usual cause","Keep request bodies well under the 1 MiB cap so uploads complete quickly","If reproducible, bypass intermediate proxies to see whether one is cutting the body","Verify the client sends a correct Content-Length or valid chunked framing"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function postModel(url, body, attempts = 3) {\n  for (let i = 0; i < attempts; i++) {\n    try {\n      const res = await fetch(url, { method: 'POST', body });\n      if (res.ok || res.status !== 400 || !res.statusText.includes('body')) return res;\n      throw new Error(await res.text());\n    } catch (e) {\n      if (i === attempts - 1) throw e; // transient body-read failures\n      await new Promise(r => setTimeout(r, 300 * (i + 1)));\n    }\n  }\n}","preventionTips":["Keep POST bodies under the 1 MiB server-side cap","Send Content-Length (or let fetch set it) rather than manual chunking","Avoid submitting from tabs that may be closed mid-request","Treat single 'Failed to read request body' responses as transient; only investigate when repeated"],"tags":["go","http","request-body","network"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}