Showcase · Error package · Go

errors

The error package the estate owns: stack traces, user-facing hints, structured attributes for logging, and an aggregate that behaves like the standard library's, so nothing goes missing below a Join. It imports nothing outside the standard library, and a test enforces that.

v0.3.0, released Pre-1.0Covers 2 projects

DocsRepository

The problem it exists for

The estate used to sit on cockroachdb/errors, chosen for its features and for being well maintained. The features held up and the maintenance didn’t: a bug that hid user hints underneath a joined error went unanswered for months, and a docs typo sat open for two years while a fix CockroachDB itself needed merged in five days (Merged in five days, open in seven hundred tells that story).

And it was expensive for what it gave. The estate called about ten of its functions, and for those paid 46 of go/forge’s 376 transitive packages, a deprecated protobuf library among them (spec 0001).

How it works

The same names, nothing underneath

go/errors keeps the names and signatures the estate was already using, so moving across was mostly a change of import path. It imports the standard library and nothing else, and a test fails the build if that ever changes. On top of that it adds what was missing: hints a user can act on, structured attributes that carry straight into logs, sentinel errors that don’t capture a stack, and a join that behaves the way the standard library’s does.

A handler that doesn’t exit for you

go/errorhandling sits on top for command-line tools: it reports an error to the user with its hints, carries an exit code on the error itself, and hands that code back to main instead of exiting the process from inside a library.

Decisions and what they cost

  • Standard library only. The core depends on nothing else, not even “a few” dependencies. What it cost: a narrower API (attributes reuse the standard logger’s type), and an errors package the estate owns outright.
  • Source-compatible. The names stayed the same rather than being redesigned to taste. The spike that proved it moved go/forge across with one sed over 19 files and no code changes. What it cost: keeping some names I wouldn’t have picked.
  • A fixed join. Join behaves properly instead of copying the old bug. What it cost: a behaviour change at four call sites across the estate.
  • Return the code, don’t exit. The handler reports and returns an exit code; it never calls os.Exit itself (errorhandling spec 0002). What it cost: every main has to act on the code it’s given, and go-tool-base lost a workaround it had needed to flush output before exiting.

Proof in use

  • 92 projects in the estate require go/errors, from go-tool-base and colophon to 83 go/* modules, and nine use errorhandling.
  • cockroachdb/errors no longer appears in a single go.mod on the main branch, directly or indirectly.

Use it when, and when not to

Use it if you want errors with hints and structured attributes, compatible with the API you probably already know, and you’d like your error package to be the one dependency that brings no others.

It doesn’t send errors over the wire yet, so matching an error across a process boundary doesn’t work, and there’s no OpenTelemetry integration. Stacks are capped at 32 frames, a sentinel has no stack unless you wrap it, and the handler doesn’t redact or translate anything. The limitations pages for errors and errorhandling have the rest.

Where it’s going

Rendering an error onto a trace span is the next piece, and after that a wire codec, so an error can cross a process boundary and still be recognised on the other side.

What it covers

The story in posts

Last reviewed .