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/errorsfor 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
Joincompatible 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.