go-error-handlinglisted
Install: claude install-skill Markuysa/agent-skills
# Go error handling
Errors are values and part of the API contract. Decide deliberately what a caller
is allowed to *do* with each error, then pick the mechanism that supports it.
## The decision table
| Caller needs to… | Use | Caller inspects with |
| --- | --- | --- |
| Nothing — just report it | `fmt.Errorf("...: %w", err)` | nothing |
| Branch on a known condition | sentinel: `var ErrNotFound = errors.New("not found")` | `errors.Is` |
| Read structured data off the failure | typed: `type ValidationError struct{ Field string }` | `errors.As` |
| Nothing, ever — the process is broken | `panic` | — |
If you cannot name what the caller does with the distinction, do not create it.
An unexported wrapped error is cheaper than a new exported sentinel you must
support forever.
## Rules
**Wrap with `%w`, add context the caller doesn't have.**
```go
// bad — loses the cause, message repeats the function name
if err != nil {
return fmt.Errorf("getUser failed: %v", err)
}
// good — preserves the chain, adds the identifier that was in scope
if err != nil {
return fmt.Errorf("load user %s: %w", id, err)
}
```
Use `%v` (not `%w`) deliberately when you want to *stop* the chain — typically at
a package boundary where the inner error is an implementation detail you refuse
to make part of your API.
**Message style.** Lowercase, no trailing punctuation, no "failed to" / "error
while". Messages concatenate into a chain, so each segment is a noun phrase of
what was being done