{"record":{"id":"ddab7595a10a765a","repo":"plandex-ai/plandex","slug":"error-forwarding-request","errorCode":null,"errorMessage":"Error forwarding request","messagePattern":"Error forwarding request","errorType":"http","errorClass":null,"httpStatus":500,"severity":"critical","filePath":"app/server/handlers/proxy_helper.go","lineNumber":86,"sourceCode":"\t}\n\n\t// Copy the headers from the original request to the new request\n\tfor name, headers := range originalRequest.Header {\n\t\tfor _, h := range headers {\n\t\t\treq.Header.Add(name, h)\n\t\t}\n\t}\n\n\t// Copy the body from the original request to the new request if it's a POST or PUT\n\tif originalRequest.Method == http.MethodPost || originalRequest.Method == http.MethodPut {\n\t\treq.Body = originalRequest.Body\n\t}\n\n\t// Make the request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tlog.Printf(\"Error forwarding request: %v\\n\", err)\n\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)","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/handlers/proxy_helper.go#L68-L104","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nclient := &http.Client{}\nresp, err := client.Do(req)\n// after\nclient := &http.Client{ Timeout: 30 * time.Second }\nresp, err := client.Do(req)\nif err != nil {\n    log.Printf(\"Error forwarding request to %s: %v\", url, err)\n    if errors.Is(err, context.Canceled) {\n        return // client went away; no response to write\n    }\n    http.Error(w, \"Error forwarding request\", http.StatusBadGateway)\n    return\n}","handlingStrategy":"retry","validationCode":"// server-side pre-check before proxying\nconn, err := net.DialTimeout(\"tcp\", net.JoinHostPort(modelStream.InternalIp, port), 2*time.Second)\nif err != nil {\n    http.Error(w, \"Plan instance unreachable\", http.StatusBadGateway)\n    return\n}\nconn.Close()","typeGuard":null,"tryCatchPattern":"// client\ntry {\n  const res = await fetch(`/plans/${planId}/${branch}/connect`);\n  if (res.status === 500 || res.status === 502) {\n    await sleep(1000); // transient network blip — retry once\n    return fetch(`/plans/${planId}/${branch}/connect`);\n  }\n} catch (e) { console.error('Plan instance unreachable:', e); }","preventionTips":["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."],"tags":["network","proxy","http-500","connection"],"backgroundTag":"connection-refused","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}