{"record":{"id":"1ce68bbd8ba6b10c","repo":"gofiber/fiber","slug":"failed-to-read-response-w-1ce68b","errorCode":null,"errorMessage":"failed to read response: %w","messagePattern":"failed to read response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"middleware/idempotency/idempotency.go","lineNumber":72,"sourceCode":"\t\t}\n\t\treturn key\n\t}\n\n\t// Snapshot the configured names so later mutation of the caller's slice\n\t// cannot change an already-constructed handler. Matching uses\n\t// utils.EqualFold, so no lowercased copies are needed and comparing\n\t// against the canonical-case names fasthttp reports stays allocation-free.\n\tkeepResponseHeaders := slices.Clone(cfg.KeepResponseHeaders)\n\n\tshouldKeepHeader := func(header string) bool {\n\t\treturn slices.ContainsFunc(keepResponseHeaders, func(keep string) bool {\n\t\t\treturn utils.EqualFold(header, keep)\n\t\t})\n\t}\n\n\tmaybeWriteCachedResponse := func(c fiber.Ctx, key string) (bool, error) {\n\t\tif val, err := cfg.Storage.GetWithContext(c, key); err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to read response: %w\", err)\n\t\t} else if val != nil {\n\t\t\tvar res response\n\t\t\tif _, err := res.UnmarshalMsg(val); err != nil {\n\t\t\t\treturn false, fmt.Errorf(\"failed to unmarshal response: %w\", err)\n\t\t\t}\n\n\t\t\t_ = c.Status(res.StatusCode)\n\n\t\t\tfor header, vals := range res.Headers {\n\t\t\t\tfor _, val := range vals {\n\t\t\t\t\tc.RequestCtx().Response.Header.Add(header, val)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(res.Body) != 0 {\n\t\t\t\tif err := c.Send(res.Body); err != nil {\n\t\t\t\t\treturn true, err\n\t\t\t\t}","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/idempotency/idempotency.go#L54-L90","documentation":"Returned by idempotency.maybeWriteCachedResponse when cfg.Storage.GetWithContext fails while looking up a cached response by key. The wrapped %w is the storage backend's error (Redis, MySQL, MongoDB, etc.). The fastpath or locked-path lookup aborts and the error bubbles up through the handler.","triggerScenarios":"A non-safe request carries a valid X-Idempotency-Key and the configured Storage.GetWithContext returns a non-nil error — Redis connection lost, DB query timeout, malformed connection string, or context cancellation. Fires both on the pre-lock fastpath and post-lock re-check.","commonSituations":"Shared/distributed storage (Redis, MySQL) outage or network partition, storage credentials rotated but app not restarted, storage TTL/connection pool exhausted, or the request context cancelled by client disconnect mid-lookup.","solutions":["Check storage connectivity and credentials (Redis ping, DB ping).","Ensure the Storage instance is properly configured (pool size, timeouts, TLS).","Decide on a failure policy: fail the request (current behavior) or fall back to memory storage with the caveat that idempotency is per-process.","Add health checks/alerts on the storage backend."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Monitor storage health (Redis/DB ping) and alert on GetWithContext errors.","Size connection pools and timeouts for your traffic.","Decide idempotency's failure policy (fail-open vs fail-closed) explicitly."],"tags":["idempotency","storage","redis","distributed"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}