thanos-io/thanos · error
BUG: errors.Cause returned nil on a non-nil error
Error message
BUG: errors.Cause returned nil on a non-nil error
What it means
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).
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
Example fix
// before: writer returns a plain error with no cause
return errors.New("tsdb closed")
// after: keep a cause chain pkg/errors can resolve
return errors.Wrap(tsdb.ErrClosed, "writing samples") Defensive patterns
Strategy: type-guard
Validate before calling
// guard before classifying: ensure the chain yields a non-nil cause
if err != nil {
if c := errors.Cause(err); c == nil {
return errors.Wrap(err, "unclassifiable write failure")
}
} Type guard
func hasCause(err error) bool {
return err != nil && errors.Cause(err) != nil
} Try / catch
defer func() {
if r := recover(); r != nil {
logger.Error("receive handler panicked classifying write error", "panic", r)
// respond Unavailable so clients retry rather than lose data
}
}() Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- BUG: localAsyncWriter.RemoteWrite called without…
- unknown chunk encoding
- empty name for metric family
- empty label set detected for series
- reverse symbol lookup
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/15d3d5673bb0536a.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/receive/handler.go:1432
err := h.writer.Write(ctx, di.tenant, di.wreq.Timeseries)
if err != nil {
level.Debug(h.logger).Log("msg", "failed to write to local TSDB", "err", err, "tenant", di.tenant)
errs = append(errs, fmt.Errorf("writing %s data to local TSDB: %w", di.tenant, err))
}
}
if len(errs) > 0 {
returnErr := errs[0]
err := errors.Unwrap(returnErr)
if len(errs) > 1 {
returnErr = fmt.Errorf("got %d errors while writing to multiple tenants, first one: %w", len(errs), returnErr)
}
switch cause := errors.Cause(err); cause {
case nil:
panic("BUG: errors.Cause returned nil on a non-nil error")
default:
if isNotReady(cause) {
return nil, status.Error(codes.Unavailable, returnErr.Error())
}
if isConflict(cause) {
return nil, status.Error(codes.AlreadyExists, returnErr.Error())
}
return nil, status.Error(codes.Internal, returnErr.Error())
}
}
return &storepb.WriteResponse{}, nil
}
_, err := h.handleRequest(ctx, uint64(r.Replica), data)
if err != nil {
level.Debug(h.logger).Log("msg", "failed to handle request", "err", err)View on GitHub (pinned to 35b8b99117)