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

  1. Read the log for the underlying client.Do error — connection refused vs timeout vs context canceled distinguishes the cause.
  2. Verify the instance is up and listening: curl http://<InternalIp>:<PORT>/plans/<planId>/<branch>/<method> from the control server.
  3. Ensure stream rows are cleaned up when instances are rescheduled/die so stale IPs are not proxied.
  4. Set a sane client timeout and configure PORT identically on control server and instances.
  5. 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

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


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/ddab7595a10a765a. Report an issue: GitHub.