plandex-ai/plandex · error
Error copying response body
Error message
Error copying response body
What it means
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.
Source
Thrown at app/server/handlers/proxy_helper.go:104
http.Error(w, "Error forwarding request", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
// Copy the response headers and status code
for name, headers := range resp.Header {
for _, h := range headers {
w.Header().Add(name, h)
}
}
w.WriteHeader(resp.StatusCode)
log.Printf("Proxy forwarded successfully with status code: %d\n", resp.StatusCode)
// Copy the response body
if _, err := io.Copy(w, resp.Body); err != nil {
log.Printf("Error copying response body: %v\n", err)
http.Error(w, "Error copying response body", http.StatusInternalServerError)
}
}
View on GitHub (pinned to e2d772072e)
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).
Example fix
// before
if _, err := io.Copy(w, resp.Body); err != nil {
log.Printf("Error copying response body: %v\n", err)
http.Error(w, "Error copying response body", http.StatusInternalServerError)
}
// after
if _, err := io.Copy(w, resp.Body); err != nil {
// headers/status already sent; can only log — do not attempt http.Error
if errors.Is(r.Context().Err(), context.Canceled) {
log.Printf("Client cancelled proxy stream: %v\n", err)
} else {
log.Printf("Error copying response body: %v\n", err)
}
} Defensive patterns
Strategy: retry
Validate before calling
// client-side: preflight the upstream host before issuing a long proxied/streamed call
resp, err := http.Head("http://" + internalIP + ":" + port + "/healthz")
if err != nil || resp.StatusCode != http.StatusOK {
return fmt.Errorf("upstream host %s unavailable, re-resolve active stream first", internalIP)
} Try / catch
// Go: treat copy errors as retryable stream interruptions; check ctx to skip retries on client cancel
if _, err := io.Copy(w, resp.Body); err != nil {
if r.Context().Err() != nil {
return // client went away; do not retry or write 500
}
log.Printf("stream interrupted, retrying: %v", err)
// retry once with a fresh request before surfacing an error
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- connection to plan stream timed out due to missing heartbeat
- error listing contexts: %v
- error getting context body: %v
- error getting server models input: %v
- error fetching users: %s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/1b5abf908aaa7126.
Report an issue: GitHub.