chi's Recoverer panics instead of handling a handler panic, so a panicking request drops the connection instead of answering 500 #187
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by measurement while correcting the writer enumeration for #182. Not fixed there — it is an independent defect with its own remediation choice.
What happens
internal/server/routes.go:32installsmiddleware.Recoverer(chi v1.5.5) in front of every route. On a handler panic it is supposed to print the panic and its stack to standard error viaPrintPrettyStackand answer500. It does neither.PrintPrettyStackcallsprettyStack.parse, which locates the panic frame with:Go no longer emits that frame as
panic(0x.... It emitspanic({0x19611a0?, 0xc00003e8a0?}). The prefix never matches, the loop never breaks, and every stack line is then handed todecorateLine.decorateFuncCallLinedoesidx = strings.Index(pkg, ".")followed bymethod = pkg[idx:]with no check for-1, and panics withslice bounds out of range [-1:].That second panic escapes
Recoverer's deferred function, sow.WriteHeader(http.StatusInternalServerError)on the next line never runs.net/http's ownconn.serverecover catches it, closes the connection, and reports it through the server'sErrorLog.Reachable on any handler panic. The service uses its own
s.mw.Logging()rather than chi'sRequestLogger, soGetLogEntry(r)is nil and thePrintPrettyStackbranch is always the one taken.Measured
A
chi.NewRouter()withmiddleware.Recovererand a panicking handler, behind a realhttptestserver, in a subprocess so the process's actual fd 1 and fd 2 are captured:So, against what a reader would expect:
500 Internal Server ErrorEOFThe reported line lands on standard output at
INFO, not standard error, because the nilErrorLogfalls back to thelogpackage's default logger andinternal/loggerredirects that throughslog.SetDefault. That routing is correct and documented; what is wrong is that the line describes chi's crash rather than the fault that caused it.The record is also 2,772 bytes, above the 2,560-byte per-line ceiling
MaxAccessLogLineBytesstates. It is not client-sized, so it is a disclosed carve-out rather than a bound violation, but it is the widest line the service can be made to write.Why it matters
500, so a client cannot tell a crash from a network fault.decorateFuncCallLineand tells an operator nothing about what actually failed.sentryhttpis registered withRepanic: trueinsideRecoverer, so an operator withSENTRY_DSNset keeps that signal. An operator without one has nothing.Options
github.com/go-chi/chi/v5. The v5 pretty-printer has the samepanic(0xprefix check, so verify against the shipped Go version before assuming this is fixed rather than moved.middleware.Recovererwith a local recover middleware that logs the panic value anddebug.Stack()throughinternal/loggerand answers500. That also puts the widest line this service writes under the same handler and level as everything else, and would let the README drop the carve-out above rather than merely state it.Option 2 is the smaller dependency surface and the one that makes the stated ceiling true of one more writer, but the choice is an observability decision, which is why this is filed rather than folded into #182.
Related
The writer enumeration in
README.mdis corrected against this in #182, and #183 names the pre-correction set of writers.clawbot referenced this issue2026-08-18 03:25:33 +02:00