Auth Could Start Disabled — If the RBAC config couldn't be loaded, the app still started and served every route with no role checks. The only sign was one log line. EnableBasicAuth (no credentials, or an odd number of arguments) and EnableOAuth (a bad JWKS URL) failed the same way. All three now return an error, so the app can refuse to start: if err := app.EnableRBAC(path); err != nil { app.Logger().Fatalf("%v", err) }.
Retries After the Caller Gave Up — RetryConfig kept retrying after the caller's context was done. Each of those attempts failed immediately, yet each was logged and recorded as a 503. In one case a single closed health page produced 28 of them in 0 ms. Retrying now stops once the context is done.
Circuit Breaker Opened by Cancellations — When a caller cancelled its own request, the breaker counted it as an upstream failure, so callers going away could open the breaker for everyone. Those cancellations are no longer counted, and they no longer reset the failure count either.
New Connection for Every Health Check — The health-check response body was closed without being read, so net/http never reused the connection. Every check opened a new TCP connection, plus a new TLS handshake over TLS. The body is now drained.
ClickHouse Durations Were ~0µs — Exec, Select and AsyncInsert took their start time after the call had already returned. The app_clickhouse_stats histogram, the log duration and the span attribute now measure the real call.
Cassandra and ScyllaDB Swallowed Query Errors — QueryWithCtx never closed the gocql iterator, and closing it is the only place gocql reports a failed query. So a failed query looked like an empty result. The error is now returned.
Dgraph Mutate Didn't Persist — The transaction was committed only if the caller had set CommitNow. Otherwise nothing was written, the error was nil, and the transaction stayed open on the server. Mutate now commits.
GCP Metrics Exporter Stopped Exporting — Google rejects any request with more than 200 points. A long-running process eventually grew past that, and from then on every collection was lost. Exports are now split into requests of at most 200 points.
Kafka Query Held a Timer for 30s — A Query whose caller set no deadline got a 30s read timeout whose cancel func was thrown away. So every such query left a timer and a child context alive for the full 30s after it returned. The timeout is now cancelled as soon as the read returns.
Event Hub Health Probe Leak — Against a broker that accepts a connection but never answers, every health poll left another goroutine stuck. At most one probe runs at a time now.
GCS, FTP and Azure File Stores Raced on Reconnect — When the first connect failed, the background retry wrote the connection fields while requests read them, with no lock. GCS Stat, Open, ReadDir, Remove and Rename (and Azure Stat/Remove on a directory) also panicked if called before the store connected. Connection state is now guarded, a store that isn't connected yet returns its "not initialized" error, and a redundant connection from a concurrent Connect is closed instead of leaked.
WebSocket Stack Overflow — The handler's context was set as its own parent, so .Done(), or a .Value() lookup for any other key, overflowed the stack. A data race on c.Context between the request goroutine and App.WebSocket is fixed too.
Typed-Nil Pub/Sub Client — When kafka.New or google.New rejected a config, the typed nil they returned passed != nil checks, and a health check then called a method on it. It is now filtered out, including in GetPublisher() and GetSubscriber(), and isNil no longer panics.
EnableMCP Exited the Process — A busy MCP port triggered Fatalf during setup, which skipped every shutdown hook. The port is now claimed with net.Listen when Run starts, before any server starts. If it is busy, or MCP_PORT is outside 1-65535, startup stops cleanly: the error is logged, open datasources are released, and nothing calls os.Exit. Set MCP_PORT=0 to run without the MCP transport.
Static Files With a Trailing Slash — AddStaticFiles("static/", dir) logged success but served 404. static, /static, static/ and /static/ now all map to /static.
Silent Remote Log-Level Changes — 6 of the 30 possible level changes took effect without being logged: every change out of FATAL, plus ERROR → FATAL. All of them are logged now.
GraphQL Error Response — A failure to encode the error response is now logged, as it already was for successful responses.