sipeed/picoclaw · warning
Failed to read request body
Error message
Failed to read request body
What it means
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.
Source
Thrown at web/backend/api/models.go:311
})
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"models": models,
"total": len(models),
"default_model": defaultModel,
"provider_options": modelProviderOptionsForResponse(),
})
}
// handleAddModel appends a new model configuration entry.
//
// POST /api/models
func (h *Handler) handleAddModel(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
defer r.Body.Close()
type custom struct {
config.ModelConfig
APIKey string `json:"api_key"`
}
var mc custom
if err = json.Unmarshal(body, &mc); err != nil {
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
return
}
normalizeIncomingModelConfig(&mc.ModelConfig)
if err = validateIncomingModelConfig(&mc.ModelConfig, nil); err != nil {View on GitHub (pinned to 49183d7e8d)
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
Defensive patterns
Strategy: retry
Try / catch
async function postModel(url, body, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
const res = await fetch(url, { method: 'POST', body });
if (res.ok || res.status !== 400 || !res.statusText.includes('body')) return res;
throw new Error(await res.text());
} catch (e) {
if (i === attempts - 1) throw e; // transient body-read failures
await new Promise(r => setTimeout(r, 300 * (i + 1)));
}
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to get WeCom QR code: %w
- unexpected status %s
- request failed: %w
- read error response: %w
- read response: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/31c2fa20835cd218.
Report an issue: GitHub.