{"record":{"id":"1b5abf908aaa7126","repo":"plandex-ai/plandex","slug":"error-copying-response-body","errorCode":null,"errorMessage":"Error copying response body","messagePattern":"Error copying response body","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"app/server/handlers/proxy_helper.go","lineNumber":104,"sourceCode":"\t\thttp.Error(w, \"Error forwarding request\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\t// Copy the response headers and status code\n\tfor name, headers := range resp.Header {\n\t\tfor _, h := range headers {\n\t\t\tw.Header().Add(name, h)\n\t\t}\n\t}\n\tw.WriteHeader(resp.StatusCode)\n\n\tlog.Printf(\"Proxy forwarded successfully with status code: %d\\n\", resp.StatusCode)\n\n\t// Copy the response body\n\tif _, err := io.Copy(w, resp.Body); err != nil {\n\t\tlog.Printf(\"Error copying response body: %v\\n\", err)\n\t\thttp.Error(w, \"Error copying response body\", http.StatusInternalServerError)\n\t}\n}\n","sourceCodeStart":86,"sourceCodeEnd":107,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/handlers/proxy_helper.go#L86-L107","documentation":"In Plandex's proxy_helper.go, proxyRequest forwards an internal HTTP request to the server instance holding the active model stream and then streams the upstream response back to the client via io.Copy(w, resp.Body). This error is logged and a 500 returned when that copy fails mid-stream — meaning the response to the client could not be fully relayed. It indicates the connection broke while the body was being written, not that the upstream request itself failed.","triggerScenarios":"The upstream server (modelStream.InternalIp host) closes the connection or resets it mid-response; the requesting client disconnects so w (the ResponseWriter) fails writes; a proxy/timeout between the two servers cuts the streamed body short; or resp.Body returns a read error (e.g. unexpected EOF from chunked streaming).","commonSituations":"A running plan's server container is restarted or killed mid-stream, so the proxied streaming response dies partway; a load balancer's idle/read timeout fires during a long model stream; the CLI client is cancelled (Ctrl-C) and the server-side write to the client errors; internal IP stale after the plan moved to a different host.","solutions":["Check server logs for the underlying io.Copy error and the upstream status code logged just before it — a 200 followed by copy failure means mid-stream disconnect, not a bad request.","Verify the host at modelStream.InternalIp is still alive and the plan's server did not restart; retry the operation so a fresh active stream is resolved.","Check for client-side cancellation: if the user aborted, this server error is expected noise and can be ignored or downgraded in logging.","Increase any intermediary (load balancer / reverse proxy) read and idle timeouts that are shorter than the model stream duration.","Confirm both servers run the same PORT and can reach each other on the internal network (no firewall/NAT dropping long-lived connections)."],"exampleFix":"// before\nif _, err := io.Copy(w, resp.Body); err != nil {\n    log.Printf(\"Error copying response body: %v\\n\", err)\n    http.Error(w, \"Error copying response body\", http.StatusInternalServerError)\n}\n// after\nif _, err := io.Copy(w, resp.Body); err != nil {\n    // headers/status already sent; can only log — do not attempt http.Error\n    if errors.Is(r.Context().Err(), context.Canceled) {\n        log.Printf(\"Client cancelled proxy stream: %v\\n\", err)\n    } else {\n        log.Printf(\"Error copying response body: %v\\n\", err)\n    }\n}","handlingStrategy":"retry","validationCode":"// client-side: preflight the upstream host before issuing a long proxied/streamed call\nresp, err := http.Head(\"http://\" + internalIP + \":\" + port + \"/healthz\")\nif err != nil || resp.StatusCode != http.StatusOK {\n    return fmt.Errorf(\"upstream host %s unavailable, re-resolve active stream first\", internalIP)\n}","typeGuard":null,"tryCatchPattern":"// Go: treat copy errors as retryable stream interruptions; check ctx to skip retries on client cancel\nif _, err := io.Copy(w, resp.Body); err != nil {\n    if r.Context().Err() != nil {\n        return // client went away; do not retry or write 500\n    }\n    log.Printf(\"stream interrupted, retrying: %v\", err)\n    // retry once with a fresh request before surfacing an error\n}","preventionTips":["Retry the operation rather than treating one mid-stream failure as fatal — a restarted plan server resolves a fresh stream on the next call.","Ensure intermediary proxies/load balancers have idle timeouts longer than the longest model stream.","Monitor host health so stale InternalIp entries are detected and refreshed.","Log whether the failure was client-cancellation to avoid chasing false-positive 500s.","Keep client and server on versions with matching streaming behavior."],"tags":["go","http-proxy","network","streaming"],"backgroundTag":"response-stream-interrupted","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}