Go error handling
Skill pngdeity/apm-user-repository/packages/golang-design/.apm/skills/go-error-handling
Personal APM marketplace — skills, prompts, agents, and instructions for AI coding agents
npx -y skills add pngdeity/apm-user-repository --skill go-error-handlingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when writing Go code that returns, wraps, or handles errors — choosing between sentinel errors, custom types, and fmt.Errorf (%w vs %v), structuring error flow, or deciding whether to log or return. Also use when propagating errors across package boundaries or using errors.Is/As, even if the user doesn't ask about error strategy. Does not cover panic/recover patterns.
SKILL.md
5.3 KB, as published. Nobody here has run it
Go Error Handling
In Go, errors are values — they are created by code and consumed by code.
Choosing an Error Strategy
- System boundary (RPC, IPC, storage)? → Wrap with
%vto avoid leaking internals - Caller needs to match specific conditions? → Sentinel or typed error, wrap with
%w - Caller just needs debugging context? →
fmt.Errorf("...: %w", err) - Leaf function, no wrapping needed? → Return the error directly
Default: wrap with %w and place it at the end of the format string.
Available Scripts
scripts/check-errors.sh— Detects error handling anti-patterns: string comparison onerr.Error(), barereturn errwithout context, and log-and-return violations. Runbash scripts/check-errors.sh --helpfor options.
Core Rules
Never Return Concrete Error Types
Never return concrete error types from exported functions — a concrete nil
pointer can become a non-nil interface:
// Bad: Concrete type can cause subtle bugs
func Bad() *os.PathError { /*...*/ }
// Good: Always return the error interface
func Good() error { /*...*/ }
Error Strings
Error strings should not be capitalized and should not end with punctuation. Exception: exported names, proper nouns, or acronyms.
// Bad
err := fmt.Errorf("Something bad happened.")
// Good
err := fmt.Errorf("something bad happened")
Return Values on Error
When a function returns an error, callers must treat all non-error return values as unspecified unless explicitly documented.
Tip: Functions taking a context.Context should usually return an error
so callers can determine if the context was cancelled.
Handling Errors
When encountering an error, make a deliberate choice — do not discard
with _:
- Handle immediately — address the error and continue
- Return to caller — optionally wrapped with context
- In exceptional cases —
log.Fatalorpanic
To intentionally ignore: add a comment explaining why.
n, _ := b.Write(p) // never returns a non-nil error
For related concurrent operations, use
errgroup:
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return task1(ctx) })
g.Go(func() error { return task2(ctx) })
if err := g.Wait(); err != nil { return err }
Avoid In-Band Errors
Don't return -1, nil, or empty string to signal errors. Use multiple
returns:
// Bad: In-band error value
func Lookup(key string) int // returns -1 for missing
// Good: Explicit error or ok value
func Lookup(key string) (string, bool)
Error Flow
Handle errors before normal code. Early returns keep the happy path unindented:
// Good: Error first, normal code unindented
if err != nil {
return err
}
// normal code
Handle errors once — either log or return, never both:
Error encountered?
├─ Caller can act on it? → Return (with context via %w)
├─ Top of call chain? → Log and handle
└─ Neither? → Log at appropriate level, continue
Read references/ERROR-FLOW.md when structuring complex error flows, deciding between logging vs returning, implementing the handle-once pattern, or choosing structured logging levels.
Error Types
Advisory: Recommended best practice.
| Caller needs to match? | Message type | Use |
|---|---|---|
| No | static | errors.New("message") |
| No | dynamic | fmt.Errorf("msg: %v", val) |
| Yes | static | var ErrFoo = errors.New("...") |
| Yes | dynamic | custom error type |
Default: Wrap with fmt.Errorf("...: %w", err). Escalate to sentinels for
errors.Is(), to custom types for errors.As().
Read references/ERROR-TYPES.md when defining sentinel errors, creating custom error types, or choosing error strategies for a package API.
Error Wrapping
Advisory: Recommended best practice.
- Use
%v: At system boundaries, for logging, to hide internal details - Use
%w: To preserve error chain forerrors.Is/errors.As
Key rules: Place %w at the end. Add context callers don't have. If
annotation adds nothing, return err directly.
Read references/WRAPPING.md when deciding between %v and %w, wrapping errors across package boundaries, or adding contextual information.
Validation: After implementing error handling, run
bash scripts/check-errors.shto detect common anti-patterns. Then rungo vet ./...to catch additional issues.