Skip to content

Why we own this

This estate standardised on github.com/pkg/errors, then moved to github.com/cockroachdb/errors. That second move was made for two reasons: the feature set, and being a well-maintained project to rely on.

The features held up. The maintenance premise did not.

What changed our mind

cockroachdb/errors is not abandoned. It is maintained reactively, by one person, for one consumer:

a sentry-go fix that broke CockroachDB's own build merged in 5 days
a documentation typo open 729 days
an implementation of slog.LogValuer open 1049 days
hints unreachable through Join open, one automated reply

That is a rational way to run a library you maintain for your own product. It is a poor thing to build an estate on, because everything that matters to you and not to them waits indefinitely.

The defect that forced it

Their Join returns a stack wrapper around the aggregate, so the outermost value offers only a single Unwrap. Every reporting traversal in the library walks single-unwraps and treats a multi-error as a leaf.

The result: hints, details, context tags and telemetry keys all vanish below a Join — including when joining a single error, because it wraps even one.

We hit this twice independently before finding it. The second time, the project that had already "solved" it had shipped a helper that did not work: it tested for the standard library's aggregate shape, which is not the shape their own Join produces.

It is reported upstream. The release that followed did not address it.

What we were actually paying for

The estate used about ten functions and paid 46 transitive packages for them — including gogo/protobuf, which is deprecated, and sentry-go, which nothing here has ever called.

Those exist for CockroachDB's problem: errors that serialise across a distributed database. It is a real problem and a good library for it. It is not our problem.

Why not another library

Every candidate was a worse maintenance bet — the most recently released alternative is a single-maintainer project, and the rest range from 21 months to 6.5 years since a release.

None of them has a hint: user-facing remediation carried separately from the message. That concept is load-bearing here, so adopting an alternative meant either losing it or rebuilding it on top of someone else's package — which is this package with a dependency attached.

Why not fork

Apache-2.0 permits it, and attribution was never the obstacle. It was the wrong shape: 13,631 lines across ~30 packages, protobuf code generation and a deprecated dependency — inherited to obtain ten functions, along with someone else's architecture and their backlog.

What their backlog told us

Their open issues are, more or less, this package's design brief — written by people who use the library:

  • stop depending on pkg/errors for stack traces (open 1921 days)
  • the gogo/protobuf dependency and the init() count (1831 days)
  • obtain a stack trace without pkg/errors (1204 days)
  • make Sentry optional (275 days)
  • a Join compatible with the standard library (80 days)

Zero dependencies, native stack traces, no Sentry, and a standard-library-shaped Join are not our preferences. They are the top complaints, and several are older than some of the projects here.

What we took from elsewhere anyway

Reading tozd/go/errors — the most actively maintained alternative — was worth it even having decided not to use it. Two of its ideas are here: sentinels that carry no stack, and structured details as key/value rather than opaque strings.

Its Base family exists because a package-level var Err… = New(…) captures a stack pointing at package initialisation. We had that bug, and had not noticed, because nothing rendered stacks yet.