Map proxy errors to specific status codes instead of always 502#131
Open
alexspeller wants to merge 1 commit into
Open
Map proxy errors to specific status codes instead of always 502#131alexspeller wants to merge 1 commit into
alexspeller wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds more nuanced reverse-proxy error handling so client cancellations are recorded as 499 (instead of 5xx), and certain upstream/transport errors map to more appropriate HTTP status codes.
Changes:
- Introduces
StatusClientClosedRequest (499)and treatscontext.Canceledas a client-closed request without logging it as a proxy error. - Classifies timeout errors as
504 Gateway Timeoutand chunked-encoding parse failures as400 Bad Request. - Adds unit + end-to-end tests covering the new error classification behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/proxy_handler.go | Adds 499 status constant and expands ProxyErrorHandler to classify cancellations/timeouts/chunked-encoding errors. |
| internal/proxy_handler_test.go | Adds tests validating status codes and logging behavior, including an end-to-end disconnect case. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
httputil.ReverseProxy's ErrorHandler is invoked for several distinct
failure modes, but Thruster collapsed all of them into a 502 (logged at
info level as "Unable to proxy request"). The most damaging case is client
disconnection: when a client closes its connection before the response is
ready (an aborted fetch, a Turbo Frame swapping its src, a navigation away,
an upstream load balancer giving up), the proxy is called with a
context.Canceled error. The client is already gone so the 502 is written to
a closed socket and discarded, but the access log still records status=502,
so ordinary client cancellations show up as server errors and pollute 5xx
dashboards.
Distinguish the error cases, mirroring the handling in kamal-proxy:
* client disconnect (context.Canceled) -> 499 Client Closed Request
(nginx convention), logged at debug rather than info
* upstream timeout (net.Error with Timeout()) -> 504 Gateway Timeout
* malformed chunked encoding from the client -> 400 Bad Request
* everything else (connection refused, EOF, ...) -> 502 as before
Request-entity-too-large continues to return 413. Only genuine upstream
failures now land in the 5xx bucket.
4dbb767 to
2e9120b
Compare
rafbgarcia
approved these changes
Jun 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
httputil.ReverseProxy'sErrorHandleris invoked for several distinct failure modes, but Thruster collapses all of them into a502(logged at info level as "Unable to proxy request").The most damaging case is client disconnection. When a client closes its connection before the response is ready — an aborted
fetch, a Turbo Frame swapping itssrc, a navigation away, an upstream load balancer giving up — the proxy is called with acontext.Cancelederror. The client is already gone, so the 502 is written to a closed socket and discarded, but the access log still recordsstatus=502. Ordinary client cancellations therefore show up as server errors and pollute 5xx dashboards.Change
Distinguish the error cases, mirroring the handling in kamal-proxy's
handleProxyError:context.Canceled)net.ErrorwithTimeout())The client-disconnect case still sets a status code (rather than skipping the write) so the request is recorded in the access log — just as a 499 rather than a 502, keeping it out of the 5xx bucket. Only genuine upstream failures now land there.
Tests
Adds
proxy_handler_test.gocovering each branch, including an end-to-end test that performs a real mid-flight client cancellation through the proxy and asserts it is recorded as 499 rather than 502.