fix(server): recover from panics in RPC and UI HTTP handlers - #1877
Conversation
A panicking handler previously fell through to net/http's per-connection recover: the client's connection was dropped with no response (HTTP/2 stream reset), and the stack trace bypassed structured logging. Add connect.WithRecover on the Frontier and Admin service handlers so a panic anywhere in the handler chain returns CodeInternal with a generic message, and wrap the UI server mux with an equivalent recovery handler that returns a plain 500. Both log the panic value and stack through slog at error level; no panic details reach the caller. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe server adds centralized panic recovery for Connect RPC and UI HTTP handlers. Recovery logs panic context, hides panic details from clients, returns controlled errors, and preserves ChangesPanic recovery
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The change adds localized panic recovery for RPC and UI HTTP handlers, returning generic errors while preserving structured server logging; no actionable merge-blocking risk remains. 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: bda1a8bf-e75d-4b74-815a-5f79464ef6d5
📒 Files selected for processing (3)
pkg/server/recovery.gopkg/server/recovery_test.gopkg/server/server.go
…covery Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Coverage Report for CI Build 31782410231Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage increased (+0.1%) to 48.323%Details
Uncovered Changes
Coverage Regressions126 previously-covered lines in 4 files lost coverage.
Coverage Stats
💛 - Coveralls |
…body When a UI handler panics after writing part of a response, the status line is already on the wire: http.Error cannot change it and would only append the error text to the partial body, so the client would read a corrupt 200. Track response commitment; once committed, log the panic and re-panic with http.ErrAbortHandler so net/http drops the connection and the client sees a truncated response instead of a fake success. The wrapper forwards Flush for the reverse proxy's streaming. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address review feedback on the recovery wrapper and its coverage: - Add Unwrap on committedWriter so http.ResponseController can reach the underlying writer's Hijacker, deadline, and full-duplex methods; without it a protocol upgrade through the connect reverse proxy on the UI server would fail. - Add ReadFrom passthrough so copies into the wrapper keep the underlying writer's optimized path, which io.Copy looks up on the destination directly without walking Unwrap. - Wrap the connect server mux with the same recovery handler. The webhook bridge does its own parsing before the protected handler, and ping, health, reflection, and CORS had no recovery at all. RPC panics are still converted to connect error codes by WithRecover first; the outer wrapper only sees panics that escape it. - Rename uiPanicRecovery to httpPanicRecovery since it now fronts both servers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 03e7e87a-e04d-437e-84ce-32e176594906
📒 Files selected for processing (3)
pkg/server/recovery.gopkg/server/recovery_test.gopkg/server/server.go
🚧 Files skipped from review as they are similar to previous changes (1)
- pkg/server/recovery.go
io.Copy prefers the source's WriteTo over the destination's ReadFrom, and strings.Reader implements WriteTo, so the test never reached ReadFrom. Call it directly so the test exercises the method it checks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Problem
Frontier has no panic recovery in the request path. When a handler panics, the only net is Go's built-in per-connection recover in
net/http:INTERNAL_ERRORstream reset), instead of an RPC error.msg— invisible to anything watching for error-level logs, and with no request context.Reproduced by planting a deliberate
panic()in a handler:Fix
connect.WithRecover(...)when building the Frontier and Admin service handlers. The recover callback logs the procedure, panic value, and stack through slog at error level, and returnsCodeInternalwith a generic message. It is placed before the interceptor chain so it sits outermost and also catches panics thrown from interceptors, not just handlers./configs,/frontier-connect/proxy, SPA routes) with an equivalent recovery handler that returns a plain 500, sinceWithRecoveronly covers connect handlers. It re-panics onhttp.ErrAbortHandler, matching net/http's own contract.Out of scope: panics on internal background goroutines (audit log listener, shutdown watchers) — those are not HTTP calls and need their own recover treatment separately.
After the fix
Same deliberately panicking handler:
and one structured log entry:
Tests
"code":"internal", no panic detail in the body, and an error-level log entry carrying the procedure and stack.http.ErrAbortHandler.🤖 Generated with Claude Code