go-code-review
Use when reviewing Go code or checking code against community style standards. Also use proactively before submitting a Go PR or…
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
$ npx -y skills add cxuu/golang-skills --skill go-error-handling --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/go-error-handlingContext preview
The summary Claude sees to decide when to auto-load this skill.
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
name: go-error-handling description: 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 (see go-defensive). allowed-tools: Bash(bash:*)
> Compatibility: `errors.Is`, `errors.As`, and `%w` wrapping require Go 1.13+; structured logging examples may use `log/slog` from Go 1.21+.
In Go, [errors are values](https://go.dev/blog/errors-are-values) — they are created by code and consumed by code.
1. System boundary (RPC, IPC, storage)? → Wrap with `%v` to avoid leaking internals 2. Caller needs to match specific conditions? → Sentinel or typed error, wrap with `%w` 3. Caller just needs debugging context? → `fmt.Errorf("...: %w", err)` 4. Leaf function, no wrapping needed? → Return the error directly
**Default**: wrap with `%w` and place it at the end of the format string.
---
**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 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")For displayed messages (logs, test failures, API responses), capitalization is appropriate.
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.
---
When encountering an error, make a **deliberate choice** — do not discard with `_`:
1. **Handle immediately** — address the error and continue 2. **Return to caller** — optionally wrapped with context 3. **In exceptional cases** — `log.Fatal` or `panic`
To intentionally ignore: add a comment explaining why.
n, _ := b.Write(p) // never returns a non-nil error
For related concurrent operations, use [`errgroup`](https://pkg.go.dev/golang.org/x/sync/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 }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)
This prevents callers from writing `Parse(Lookup(key))` — it causes a compile-time error since `Lookup(key)` has 2 outputs.
---
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
---
> **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()`.
---
> **Advisory**: Recommended best practice.
**Key rules**: Place `%w` at the end. Add context callers don't have. If annotation adds nothing, return `err` directly.
> **Validation**: After implementing error handling, run `bash scripts/check-errors.sh` to detect common anti-patterns. Then run `go vet ./...` to catch additional issues.
---
AI Agent Skills for writing idiomatic, production-quality Go code. 20 modular skills teach AI coding assistants Go best practices derived from: Google Go Style Guide Effective Go Uber Go Style Guide Go Wiki CodeReviewComments Skills are tuned following
Use when reviewing Go code or checking code against community style standards. Also use proactively before submitting a Go PR or…
Use when writing concurrent Go code — goroutines, channels, mutexes, or thread-safety guarantees. Also use when parallelizing…
Use when working with context.Context in Go — placement in signatures, propagating cancellation and deadlines, and storing values…
Use when writing conditionals, loops, or switch statements in Go — including if with initialization, early returns, for loop…
Use when working with Go slices, maps, or arrays — choosing between new and make, using append, declaring empty slices (nil vs…
Use when declaring or initializing Go variables, constants, structs, or maps — including var vs :=, reducing scope with if-init,…