{"record":{"id":"f3b27c983654d736","repo":"router-for-me/CLIProxyAPI","slug":"failed-to-read-plugin-management-request-body","errorCode":null,"errorMessage":"failed to read plugin management request body","messagePattern":"failed to read plugin management request body","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"internal/pluginhost/management.go","lineNumber":246,"sourceCode":"// ServeManagementHTTP dispatches an authenticated Management API request to a plugin route.\nfunc (h *Host) ServeManagementHTTP(w http.ResponseWriter, r *http.Request) bool {\n\tif h == nil || w == nil || r == nil || r.URL == nil {\n\t\treturn false\n\t}\n\tkey := managementRouteKey(r.Method, r.URL.Path)\n\th.mu.Lock()\n\trecord, okRoute := h.managementRoutes[key]\n\th.mu.Unlock()\n\tif !okRoute || record.route.Handler == nil || h.isPluginFused(record.pluginID) {\n\t\treturn false\n\t}\n\n\tvar body []byte\n\tif r.Body != nil {\n\t\tvar errRead error\n\t\tbody, errRead = io.ReadAll(r.Body)\n\t\tif errRead != nil {\n\t\t\thttp.Error(w, \"failed to read plugin management request body\", http.StatusBadRequest)\n\t\t\treturn true\n\t\t}\n\t\tif errClose := r.Body.Close(); errClose != nil {\n\t\t\tlog.Warnf(\"pluginhost: failed to close plugin management request body: %v\", errClose)\n\t\t}\n\t}\n\tr.Body = io.NopCloser(bytes.NewReader(body))\n\n\tresp, errHandle := h.callManagementHandler(r.Context(), record, pluginapi.ManagementRequest{\n\t\tMethod:  r.Method,\n\t\tPath:    r.URL.Path,\n\t\tHeaders: cloneHeader(r.Header),\n\t\tQuery:   cloneValues(r.URL.Query()),\n\t\tBody:    bytes.Clone(body),\n\t})\n\tif errHandle != nil {\n\t\tlog.Warnf(\"pluginhost: management handler %s failed: %v\", record.pluginID, errHandle)\n\t\thttp.Error(w, \"plugin management handler failed\", http.StatusBadGateway)","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginhost/management.go#L228-L264","documentation":"The plugin host management proxy reads the full request body before forwarding it to a plugin's management handler. This 400 response means io.ReadAll on the request body failed, which almost always indicates the client disconnected mid-request or sent a malformed/chunked body that violated HTTP framing. The body is buffered so it can be replayed to the plugin handler, hence the explicit read step.","triggerScenarios":"Any management API request routed to a plugin management endpoint (a path registered by a plugin) where the client aborts the connection while the body is being transmitted, or where the body is truncated/corrupt (bad Content-Length, broken chunked encoding). Also possible if an intermediary proxy closes the connection early.","commonSituations":"Client-side timeouts or cancellations (curl aborted, HTTP client context cancelled), flaky connections between a reverse proxy and the server, load-test tools closing keep-alive connections aggressively, or custom scripts sending hand-crafted requests with wrong Content-Length.","solutions":["Retry the management request with a well-formed body and correct Content-Length/chunked encoding","Check client-side timeouts and cancellation: increase the HTTP client timeout or avoid cancelling the context while the request is in flight","Inspect any intermediary proxy (nginx, traefik) between client and server for early connection teardown or request buffering limits","Enable trace-level logs to confirm whether the client is resetting the connection (RST) mid-body"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before sending, make sure the body is well-formed and sized:\nconst body = JSON.stringify(payload);\nfetch(url, { method: 'POST', headers: { 'Content-Length': String(Buffer.byteLength(body)) }, body });","typeGuard":null,"tryCatchPattern":"// Treat 400-on-read as transient client-side framing failure: verify body integrity, then retry once.\nconst res = await fetch(url, {...});\nif (res.status === 400) {\n  const text = await res.text();\n  if (text.includes('failed to read plugin management request body')) {\n    return retryOnce(sameRequest); // with identical, verified body\n  }\n  throw new Error(text);\n}","preventionTips":["Set sane HTTP client timeouts instead of hard-cancelling requests mid-body","Avoid hand-crafted raw sockets for management API calls; use a real HTTP client","Keep request bodies small enough for intermediary proxy buffer limits","Log client aborts separately so transient disconnects are not mistaken for server bugs"],"tags":["plugin","http","request-body","client-disconnect"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}