Golang Error Handling
Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe
- Skill
- Guidelines
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to Go error management, emphasizing maintainability and observability. It covers the full lifecycle of an error, from creation and wrapping to inspection and logging. By adhering to the single handling rule and leveraging modern Go features like slog and errors.Join, developers can create robust systems that provide clear diagnostic information without cluttering logs with duplicate entries.
What it does
- Defines best practices for error creation, wrapping, and inspection.
- Enforces the single handling rule to prevent duplicate log noise.
- Provides patterns for structured logging using slog.
- Guides the appropriate use of panic and recover for unrecoverable states.
- Supports auditing codebases for common error handling anti-patterns.
- Integrates with samber/oops for enhanced production error context.
When to use it
- When writing new Go code that requires robust error propagation.
- During code reviews to identify swallowed or improperly handled errors.
- When auditing existing codebases for reliability and observability improvements.
- When setting up production logging and error monitoring pipelines.
Advantages
- Reduces log noise by enforcing the single handling rule.
- Improves debuggability with structured context and stack traces.
- Ensures consistent error handling patterns across large teams.
- Facilitates better integration with modern log aggregation tools.
Usage examples
- “Review my Go function for idiomatic error handling.”
- The agent will analyze the code for swallowed errors, missing context wrapping, and ensure the single handling rule is followed, suggesting improvements for better observability.
- “Audit my codebase for panic/recover usage.”
- The agent will scan the project for improper panic usage and verify that recovery mechanisms are correctly implemented at goroutine boundaries.
- “How should I wrap errors in my service layer?”
- The agent will provide examples using fmt.Errorf with %w to maintain error chains, ensuring context is added without losing the original error type.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/samber/golang-error-handling/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip golang-error-handling.zip -d .agents/skills
Documentation by samber
Persona: You are a Go reliability engineer. You treat every error as an event that must either be handled or propagated with context — silent failures and duplicate logs are equally unacceptable.
Orchestration mode: Fan out the five category sub-agents described in the "Parallelizing Error Handling Audits" section (creation, wrapping, single-handling rule, panic/recover, structured logging) for auditing error handling across a large codebase, and consolidate their findings. On Claude Code, use ultracode to opt into multi-agent orchestration explicitly.
Modes:
Coding mode — writing new error handling code. Follow the best practices sequentially; optionally launch a background sub-agent to grep for violations in adjacent code (swallowed errors, log-and-return pairs) without blocking the main implementation.
Review mode — reviewing a PR's error handling changes. Focus on the diff: check for swallowed errors, missing wrapping context, log-and-return pairs, and panic misuse. Sequential.
Audit mode — auditing existing error handling across a codebase. Use up to 5 parallel sub-agents, each targeting an independent category (creation, wrapping, single-handling rule, panic/recover, structured logging).
Community default. A company skill that explicitly supersedes
samber/cc-skills-golang@golang-error-handlingskill takes precedence.
Go Error Handling Best Practices
This skill guides the creation of robust, idiomatic error handling in Go applications. Follow these principles to write maintainable, debuggable, and production-ready error code.
Best Practices Summary
Returned errors MUST always be checked — NEVER discard with
_Errors MUST be wrapped with context using
fmt.Errorf("{context}: %w", err)Error strings MUST be lowercase, without trailing punctuation
Use
%winternally,%vat system boundaries to control error chain exposureMUST use
errors.Isfor sentinel matching anderrors.As/errors.AsTypefor typed chain inspection instead of direct comparison or bare type assertions. For Go 1.26+, prefererrors.AsType[T](err)whenTimplementserror; useerrors.As(err, &target)for Go <1.26 or for non-error interface targets.SHOULD use
errors.Join(Go 1.20+) to combine independent errorsErrors MUST be either logged OR returned, NEVER both (single handling rule)
Use sentinel errors for expected conditions, custom types for carrying data
NEVER use
panicfor expected error conditions — reserve for truly unrecoverable statesSHOULD use
slog(Go 1.21+) for structured error logging — notfmt.Printlnorlog.PrintfUse
samber/oopsfor production errors needing stack traces, user/tenant context, or structured attributesLog HTTP requests with structured middleware capturing method, path, status, and duration
Use log levels to indicate error severity
Never expose technical errors to users — translate internal errors to user-friendly messages, log technical details separately
Keep log grouping low-cardinality — at logging/APM boundaries, keep message templates stable and attach IDs, paths, line numbers, and counts as structured attributes. Error values may include useful operational context, but avoid putting high-cardinality data into the stable log message used for grouping.
Detailed Reference
Error Creation — How to create errors that tell the story: error messages should be lowercase, no punctuation, and describe what happened without prescribing action. Covers sentinel errors (one-time preallocation for performance), custom error types (for carrying rich context), and the decision table for which to use when.
Error Wrapping and Inspection — Why
fmt.Errorf("{context}: %w", err)beatsfmt.Errorf("{context}: %v", err)(chains vs concatenation). How to inspect chains witherrors.Is,errors.As, and Go 1.26+errors.AsTypefor type-safe error handling, anderrors.Joinfor combining independent errors.Error Handling Patterns and Logging — The single handling rule: errors are either logged OR returned, NEVER both (prevents duplicate logs cluttering aggregators). Panic/recover design,
samber/oopsfor production errors, andslogstructured logging integration for APM tools.
Parallelizing Error Handling Audits
When auditing error handling across a large codebase, use up to 5 parallel sub-agents — each targets an independent error category:
Sub-agent 1: Error creation — validate
errors.New/fmt.Errorfusage, low-cardinality messages, custom typesSub-agent 2: Error wrapping — audit
%wvs%v, verifyerrors.Is/errors.AspatternsSub-agent 3: Single handling rule — find log-and-return violations, swallowed errors, discarded errors (
_)Sub-agent 4: Panic/recover — audit
panicusage, verify recovery at goroutine boundariesSub-agent 5: Structured logging — verify
slogusage at error sites, check for PII in error messages
Cross-References
→ See
samber/cc-skills-golang@golang-samber-oopsfor full samber/oops API, builder patterns, and logger integration→ See
samber/cc-skills-golang@golang-observabilityfor structured logging setup, log levels, and request logging middleware→ See
samber/cc-skills-golang@golang-safetyfor nil interface trap and nil error comparison pitfalls→ See
samber/cc-skills-golang@golang-namingfor error naming conventions (ErrNotFound, PathError)→ See
samber/cc-skills-golang@golang-continuous-integrationskill for automated AI-driven code review in CI using these guidelines
References
Files
- ATTRIBUTION.mdAdded by AutoGineer920 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md7.2 KiB
- evals/evals.json15.4 KiB
- references/error-creation.md5.6 KiB
- references/error-handling.md4.5 KiB
- references/error-wrapping.md3.8 KiB
references/error-creation.md
Error Creation
Table of Contents
Errors as Values
Error String Conventions
Creating Errors
errors.New— static error messagesfmt.Errorf— dynamic error messagesDecision table: which error strategy to use
Low-Cardinality Error Messages
Custom Error Types
Custom types that wrap other errors
Errors as Values
Go treats errors as ordinary values implementing the error interface:
type error interface {
Error() string
}This means errors are returned, not thrown. Every function that can fail returns an error as its last return value, and every caller must check it.
// ✗ Bad — silently discarding errors
data, _ := os.ReadFile("config.yaml")
// ✗ Bad — only checking in some branches
result, err := doSomething()
fmt.Println(result) // using result without checking err
// ✓ Good — always check before using other return values
data, err := os.ReadFile("config.yaml")
if err != nil {
return fmt.Errorf("reading config: %w", err)
}Error String Conventions
Error strings MUST be lowercase, without trailing punctuation, and should not duplicate the context that wrapping will add.
// ✗ Bad — capitalized, punctuation, redundant prefix
return errors.New("Failed to connect to database.")
return fmt.Errorf("UserService: failed to fetch user: %w", err)
// ✓ Good — lowercase, no punctuation, concise
return errors.New("connection refused")
return fmt.Errorf("fetching user: %w", err)When errors are wrapped through multiple layers, each layer adds its own prefix. The result reads like a chain:
creating order: charging card: connecting to payment gateway: connection refusedCreating Errors
errors.New — static error messages
var ErrNotFound = errors.New("not found")
var ErrUnauthorized = errors.New("unauthorized")fmt.Errorf — dynamic error messages
import "github.com/samber/oops"
// ✗ Avoid at log/APM boundaries — each user/tenant combo becomes a unique group
return fmt.Errorf("user %s not found in tenant %s", userID, tenantID)
// ✓ Prefer for grouped production errors — static message, variable data as structured attributes
return oops.With("user_id", userID).With("tenant_id", tenantID).Errorf("user not found")See Low-Cardinality Error Messages for why this matters.
Decision table: which error strategy to use
| Situation | Strategy | Example |
|---|---|---|
| Caller needs to match a specific condition | Sentinel error (errors.New as package var) | var ErrNotFound = errors.New("not found") |
| Caller needs to extract structured data | Custom error type | type ValidationError struct { Field, Msg string } |
| Error is purely informational, not matched on | fmt.Errorf or errors.New | fmt.Errorf("connecting to %s: %w", addr, err) |
| Need stack traces, user context, structured attrs | samber/oops | See Why Use samber/oops |
Low-Cardinality Error Messages
APM and log aggregation tools (Datadog, Loki, Sentry) commonly group events by the logged message or exception fingerprint. When the stable log message contains variable data, every unique combination can create a separate group — dashboards become noisy and alerting breaks.
import "github.com/samber/oops"
// ✗ Bad at the log boundary — each file/line combo can create a unique group
fmt.Errorf("error in %s at line %d of the csv", csvPath, line)
// ✓ Good (stdlib) — static error, structured attributes at the log site
err := errors.New("csv parsing error")
// ... later, at the logging boundary:
slog.Error("csv parsing failed", "error", err, "csv_file_path", csvPath, "csv_file_line", line)
// ✓ Good (samber/oops, external dependency) — attributes travel with the error
oops.With("csv_file_path", csvPath).With("csv_file_line", line).Errorf("csv parsing error")The stdlib approach works but scatters context: the error travels up the stack and the handler logging it may no longer have access to the variable data. samber/oops (external dependency github.com/samber/oops) solves this by attaching structured attributes directly to the error, so they're available wherever the error is eventually logged.
Static wrapping prefixes are fine — fmt.Errorf("fetching user: %w", err) is low-cardinality because the prefix never changes. Dynamic context in returned errors is sometimes useful for CLI output or debugging, but production logging should keep the grouping message stable and attach IDs, paths, counts, and other variable data as structured attributes.
Custom Error Types
Create custom error types when callers need to extract structured data from errors.
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Usage
func validateAge(age int) error {
if age < 0 {
return &ValidationError{Field: "age", Message: "must be non-negative"}
}
return nil
}Custom types that wrap other errors
Implement Unwrap() so errors.Is and errors.As can traverse the chain:
type QueryError struct {
Query string
Err error
}
func (e *QueryError) Error() string {
return fmt.Sprintf("query %q: %v", e.Query, e.Err)
}
func (e *QueryError) Unwrap() error {
return e.Err
}Requirements and compatibility
Written for: Claude Code, OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Declares allowed tools, Claude Code frontmatter
The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- samber
- Publisher
- Samuel Berthe (Community)
- Distributed by
- Samuel Berthe
- Source
- samber/cc-skills-golang · skills/golang-error-handling · commit bb1d43d
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-error-handling.zip