The obvious fix for a recurring 502 is to improve the error shown to the client. That would have been the wrong fix.
The dashboard already returned safe client facing wording. The real problem was that the useful failure detail disappeared before reaching the server log. A queue fill request could fail with a 502, while the evidence needed to diagnose it was swallowed inside a chained ResearchAgentError.
That distinction matters. A client needs an error it can safely display. An operator needs the actual failure mode. Treating those as the same message either exposes internal detail to the client or leaves the server blind. Neither is acceptable.
I kept the existing client mapping unchanged. Its wording remained stable, so the dashboard contract did not move and internal execution details did not leak across the boundary.
Then I logged the chained ResearchAgentError on the server. That error contains the detail that separates several failures which otherwise collapse into the same 502: a CLI exit status, a timeout, or output that could not be parsed.
Those cases may look identical from the browser, but they imply very different investigations. A CLI exit status points toward the process invocation or its environment. A timeout points toward duration and queue behavior. Unparseable output points toward the boundary between the research agent and the code consuming its response.
One safe message. Three materially different causes. Logging only the mapped client error erased that difference and turned recurring failures into guesswork.
The tradeoff was deliberate. Server logs now contain more operational detail, while the client receives exactly the same controlled wording as before. The change improves diagnosis without expanding the public error surface or forcing client code to understand internal agent failures.
I would do one thing differently next time: define the observability boundary when introducing the error mapping, not after repeated 502s expose the gap. Safe client errors and detailed server errors are not competing goals. They are separate outputs for separate audiences, and the exception chain should reach the one place where it can actually help.
Top comments (0)