plandex-ai/plandex · critical
Error forwarding request
Error message
Error forwarding request
What it means
The proxy request was constructed and sent, but client.Do failed — the instance hosting the plan could not be reached or did not respond in time. This is a network-level failure between the control server and the plan instance, surfaced as HTTP 500 "Error forwarding request". The default HTTP client has no timeout, so this also fires on hangs that are eventually aborted by the request context.
Source
Thrown at app/server/handlers/proxy_helper.go:86
}
// Copy the headers from the original request to the new request
for name, headers := range originalRequest.Header {
for _, h := range headers {
req.Header.Add(name, h)
}
}
// Copy the body from the original request to the new request if it's a POST or PUT
if originalRequest.Method == http.MethodPost || originalRequest.Method == http.MethodPut {
req.Body = originalRequest.Body
}
// Make the request
resp, err := client.Do(req)
if err != nil {
log.Printf("Error forwarding request: %v\n", err)
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
- Read the log for the underlying client.Do error — connection refused vs timeout vs context canceled distinguishes the cause.
- Verify the instance is up and listening: curl http://<InternalIp>:<PORT>/plans/<planId>/<branch>/<method> from the control server.
- Ensure stream rows are cleaned up when instances are rescheduled/die so stale IPs are not proxied.
- Set a sane client timeout and configure PORT identically on control server and instances.
- Check network policy/firewall allows the control server to reach instance IPs on PORT; add retries for idempotent methods.
Example fix
// before
client := &http.Client{}
resp, err := client.Do(req)
// after
client := &http.Client{ Timeout: 30 * time.Second }
resp, err := client.Do(req)
if err != nil {
log.Printf("Error forwarding request to %s: %v", url, err)
if errors.Is(err, context.Canceled) {
return // client went away; no response to write
}
http.Error(w, "Error forwarding request", http.StatusBadGateway)
return
} Defensive patterns
Strategy: retry
Validate before calling
// server-side pre-check before proxying
conn, err := net.DialTimeout("tcp", net.JoinHostPort(modelStream.InternalIp, port), 2*time.Second)
if err != nil {
http.Error(w, "Plan instance unreachable", http.StatusBadGateway)
return
}
conn.Close() Try / catch
// client
try {
const res = await fetch(`/plans/${planId}/${branch}/connect`);
if (res.status === 500 || res.status === 502) {
await sleep(1000); // transient network blip — retry once
return fetch(`/plans/${planId}/${branch}/connect`);
}
} catch (e) { console.error('Plan instance unreachable:', e); } Prevention
- Clean up stream rows when instances are rescheduled or die so stale IPs aren't proxied.
- Set an explicit http.Client Timeout instead of relying on request-context cancellation.
- Verify network policy allows control-server → instance traffic on PORT.
- Distinguish error types in logs (refused/timeout/canceled) to pick retry vs cleanup.
When it happens
Trigger: Instance at modelStream.InternalIp is down, restarting, or deregistered; wrong IP stored in the stream row (stale after instance rescheduled); network policy/firewall blocks the internal port; PORT env differs between control server and instance so the port doesn't match; request context canceled by the original client disconnecting mid-proxy.
Common situations: Kubernetes pod replaced while stream row still points to old pod IP; security groups/NACOs blocking pod-to-pod traffic; instance listening on a different PORT than the proxy assumes; client closed the browser tab causing context cancellation; DNS/ARP issues on the internal network.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
- Error reading request body
- Error listing projects:
- Error getting active model stream
- Error creating request for proxy
- connection to plan stream timed out due to missing heartbeat
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/ddab7595a10a765a.
Report an issue: GitHub.