{"record":{"id":"15d3d5673bb0536a","repo":"thanos-io/thanos","slug":"bug-errors-cause-returned-nil-on-a-non-nil-error","errorCode":null,"errorMessage":"BUG: errors.Cause returned nil on a non-nil error","messagePattern":"BUG: errors\\.Cause returned nil on a non-nil error","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/receive/handler.go","lineNumber":1432,"sourceCode":"\t\t\terr := h.writer.Write(ctx, di.tenant, di.wreq.Timeseries)\n\t\t\tif err != nil {\n\t\t\t\tlevel.Debug(h.logger).Log(\"msg\", \"failed to write to local TSDB\", \"err\", err, \"tenant\", di.tenant)\n\n\t\t\t\terrs = append(errs, fmt.Errorf(\"writing %s data to local TSDB: %w\", di.tenant, err))\n\t\t\t}\n\t\t}\n\n\t\tif len(errs) > 0 {\n\t\t\treturnErr := errs[0]\n\t\t\terr := errors.Unwrap(returnErr)\n\n\t\t\tif len(errs) > 1 {\n\t\t\t\treturnErr = fmt.Errorf(\"got %d errors while writing to multiple tenants, first one: %w\", len(errs), returnErr)\n\t\t\t}\n\n\t\t\tswitch cause := errors.Cause(err); cause {\n\t\t\tcase nil:\n\t\t\t\tpanic(\"BUG: errors.Cause returned nil on a non-nil error\")\n\t\t\tdefault:\n\t\t\t\tif isNotReady(cause) {\n\t\t\t\t\treturn nil, status.Error(codes.Unavailable, returnErr.Error())\n\t\t\t\t}\n\t\t\t\tif isConflict(cause) {\n\t\t\t\t\treturn nil, status.Error(codes.AlreadyExists, returnErr.Error())\n\t\t\t\t}\n\t\t\t\treturn nil, status.Error(codes.Internal, returnErr.Error())\n\t\t\t}\n\t\t}\n\n\t\treturn &storepb.WriteResponse{}, nil\n\n\t}\n\n\t_, err := h.handleRequest(ctx, uint64(r.Replica), data)\n\tif err != nil {\n\t\tlevel.Debug(h.logger).Log(\"msg\", \"failed to handle request\", \"err\", err)","sourceCodeStart":1414,"sourceCodeEnd":1450,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/receive/handler.go#L1414-L1450","documentation":"This is a deliberate invariant panic: after wrapping, the handler unwraps the first error and calls errors.Cause(err) (github.com/pkg/errors); if Cause returns nil for a non-nil error the code panics, because the subsequent cause-switch cannot classify the failure. It guards against accidentally wrapping a non-pkg/errors error (e.g. a plain fmt.Errorf without %w or a stdlib error with no Cause support).","triggerScenarios":"The first collected write error, after errors.Unwrap, has no pkg/errors cause: errors.Cause returns the error itself normally, but nil is returned when the chain is broken — e.g. errs[0] is nil, or the wrapped cause at handler.go:1432 does not support Cause and Unwrap produced nil.","commonSituations":"A writer implementation returning a bare error that escapes the wrapping contract; a code change replacing pkg/errors wrapping with plain errors.New somewhere in the Write path; genuine Thanos bug.","solutions":["File/inspect a Thanos bug — this panic indicates a violated internal wrapping contract, not a user error","Find which writer.Write error chain produced nil Cause and fix it to wrap with pkg/errors (errors.Wrapf) or fmt.Errorf with %w","As a workaround, ensure h.writer returns errors created/wrapped via pkg/errors so errors.Cause is defined","Upgrade Thanos to a version where the receive error classification handles non-pkg/errors chains"],"exampleFix":"// before: writer returns a plain error with no cause\nreturn errors.New(\"tsdb closed\")\n// after: keep a cause chain pkg/errors can resolve\nreturn errors.Wrap(tsdb.ErrClosed, \"writing samples\")","handlingStrategy":"type-guard","validationCode":"// guard before classifying: ensure the chain yields a non-nil cause\nif err != nil {\n    if c := errors.Cause(err); c == nil {\n        return errors.Wrap(err, \"unclassifiable write failure\")\n    }\n}","typeGuard":"func hasCause(err error) bool {\n    return err != nil && errors.Cause(err) != nil\n}","tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        logger.Error(\"receive handler panicked classifying write error\", \"panic\", r)\n        // respond Unavailable so clients retry rather than lose data\n    }\n}()","preventionTips":["Always wrap errors in the Write path with pkg/errors (errors.Wrap/Wrapf) so errors.Cause works","Never return bare errors.New from writer implementations consumed by receive","Report this panic upstream — it signals a code bug, not a runtime condition","Keep the error-classification switch defensive: default-to-Unavailable instead of panicking"],"tags":["panic","invariant","bug"],"backgroundTag":"internal-invariant-violation","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}