Handle and Wrap Every Error with Context in Go
Flags Go errors that are discarded, logged and dropped, shadowed, or wrapped with %v, so failures surface with context and errors.Is keeps working.
What it flags
func (s *Importer) ImportOrders(ctx context.Context, path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("open %s: %v", path, err)
}
defer f.Close()
var orders []Order
_ = json.NewDecoder(f).Decode(&orders) // a malformed file imports nothing and returns nil
for _, o := range orders {
if err := s.store.Save(ctx, o); err != nil {
log.Printf("save order %s: %v", o.ID, err)
}
}
return err
}var ErrInvalidImport = errors.New("invalid import file")
func (s *Importer) ImportOrders(ctx context.Context, path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("open %s: %w", path, err)
}
defer f.Close()
var orders []Order
if err := json.NewDecoder(f).Decode(&orders); err != nil {
return fmt.Errorf("%w: decode %s: %w", ErrInvalidImport, path, err)
}
for _, o := range orders {
if err := s.store.Save(ctx, o); err != nil {
return fmt.Errorf("save order %s: %w", o.ID, err)
}
}
return nil
}Why it matters
An error discarded at one layer turns into wrong data, a partial write, or a nil pointer panic several calls later, far from the cause. Wrapping with %v instead of %w breaks errors.Is and errors.As upstream, so a caller that retries on a timeout or returns 404 on sql.ErrNoRows takes the wrong branch without any sign of it. These bugs pass review because each line looks reasonable, and errcheck in its default configuration does not report errors assigned to the blank identifier.
Agent instructions
cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.
AGENTS.md
Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.
## Handle and Wrap Every Error with Context in Go
Applies to `**/*.go`.
Review Go error handling for errors that are dropped, masked, or stripped of the context callers need.
Flag changes that:
- Discard an error with `_ =` or `_` in a multi-value assignment, then use the other results as if the call succeeded.
- Check `err != nil` and only log it, then continue with a zero, nil, or partial value instead of returning or handling the failure.
- Return a nil error from a failure branch, such as `if err != nil { return nil, nil }`, or return an outer `err` that is nil because the failing call assigned to a different variable.
- Shadow `err` with `:=` inside an `if`, `for`, or closure, so the error never reaches the outer variable that is checked, returned, or read by a deferred function.
- Wrap with `fmt.Errorf` using `%v`, `%s`, or `err.Error()` where callers need the cause, which breaks `errors.Is` and `errors.As`.
- Compare errors with `==`, type-assert them with `err.(*T)`, or match `err.Error()` text when the error may be wrapped, instead of `errors.Is` or `errors.As`.
- Call `log.Fatal` or `os.Exit` in library or request-handling code, which exits without running deferred functions, or `panic` on an expected failure such as bad input or an unavailable dependency.
Do not flag:
- Ignored errors from calls documented never to fail, such as writes to `bytes.Buffer`, `strings.Builder`, or `hash.Hash`.
- `err == io.EOF` on direct `io.Reader` results, which return `io.EOF` unwrapped by contract.
- `%v` used on purpose at a package boundary to keep an underlying error out of the public API.
- An ignored error from a deferred `tx.Rollback()` that runs after `tx.Commit()`.
Checks files matching **/*.go.
More Go agents
See all Go code review standards
Run this agent on every pull request
cubic reviews every pull request against the agents you turn on and comments on the lines that break them.