go-error-handling
Use when Go error handling with error wrapping, sentinel errors, and custom error types. Use when handling errors in Go applications.
What this skill does
# Go Error Handling
Master Go's error handling patterns including error wrapping, sentinel
errors, custom error types, and the errors package for robust applications.
## Basic Error Handling
**Creating and returning errors:**
```go
package main
import (
"errors"
"fmt"
)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
}
```
**Using fmt.Errorf:**
```go
func processFile(filename string) error {
if filename == "" {
return fmt.Errorf("filename cannot be empty")
}
// Process file...
return nil
}
```
## Error Wrapping
**Wrapping errors with context (Go 1.13+):**
```go
import (
"errors"
"fmt"
"os"
)
func readConfig(path string) error {
_, err := os.Open(path)
if err != nil {
return fmt.Errorf("failed to read config: %w", err)
}
return nil
}
func main() {
err := readConfig("config.json")
if err != nil {
fmt.Println(err)
// Output: failed to read config: open config.json: no such file
}
}
```
**Unwrapping errors:**
```go
func handleError(err error) {
// Unwrap one level
unwrapped := errors.Unwrap(err)
if unwrapped != nil {
fmt.Println("Unwrapped:", unwrapped)
}
// Check if specific error is in chain
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File does not exist")
}
}
```
## Sentinel Errors
**Defining and using sentinel errors:**
```go
package main
import (
"errors"
"fmt"
)
var (
ErrNotFound = errors.New("resource not found")
ErrUnauthorized = errors.New("unauthorized access")
ErrInvalidInput = errors.New("invalid input")
)
func getUser(id int) (string, error) {
if id < 0 {
return "", ErrInvalidInput
}
if id == 0 {
return "", ErrNotFound
}
return fmt.Sprintf("user-%d", id), nil
}
func main() {
_, err := getUser(0)
if errors.Is(err, ErrNotFound) {
fmt.Println("User not found")
}
}
```
## Custom Error Types
**Implementing error interface:**
```go
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}
func validateAge(age int) error {
if age < 0 {
return &ValidationError{
Field: "age",
Message: "must be positive",
}
}
if age > 150 {
return &ValidationError{
Field: "age",
Message: "must be less than 150",
}
}
return nil
}
func main() {
err := validateAge(-5)
if err != nil {
fmt.Println(err)
}
}
```
**Type assertions with errors.As:**
```go
func handleValidation(err error) {
var validationErr *ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("Field '%s' failed: %s\n",
validationErr.Field,
validationErr.Message,
)
}
}
```
## Multi-Error Handling
**Collecting multiple errors:**
```go
type MultiError struct {
Errors []error
}
func (m *MultiError) Error() string {
if len(m.Errors) == 0 {
return "no errors"
}
if len(m.Errors) == 1 {
return m.Errors[0].Error()
}
return fmt.Sprintf("%d errors occurred: %v", len(m.Errors), m.Errors)
}
func (m *MultiError) Add(err error) {
if err != nil {
m.Errors = append(m.Errors, err)
}
}
func validateUser(name, email string, age int) error {
errs := &MultiError{}
if name == "" {
errs.Add(errors.New("name is required"))
}
if email == "" {
errs.Add(errors.New("email is required"))
}
if age < 0 {
errs.Add(errors.New("age must be positive"))
}
if len(errs.Errors) > 0 {
return errs
}
return nil
}
```
## Panic and Recover
**When to use panic:**
```go
// Panic for unrecoverable errors
func mustConnect(dsn string) *DB {
db, err := connect(dsn)
if err != nil {
panic(fmt.Sprintf("failed to connect to database: %v", err))
}
return db
}
// Recover from panics
func safeExecute(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic recovered: %v", r)
}
}()
fn()
return nil
}
```
## Error Handling Patterns
**Early return pattern:**
```go
func processRequest(id int) error {
user, err := fetchUser(id)
if err != nil {
return fmt.Errorf("fetch user: %w", err)
}
if err := validateUser(user); err != nil {
return fmt.Errorf("validate user: %w", err)
}
if err := saveUser(user); err != nil {
return fmt.Errorf("save user: %w", err)
}
return nil
}
```
**Error variable naming:**
```go
// Good: specific error names
errDB := connectDB()
if errDB != nil {
return fmt.Errorf("db connection: %w", errDB)
}
errCache := connectCache()
if errCache != nil {
return fmt.Errorf("cache connection: %w", errCache)
}
// Avoid: reusing 'err' everywhere makes debugging harder
```
## pkg/errors Pattern (Legacy)
**Using github.com/pkg/errors:**
```go
import (
"github.com/pkg/errors"
)
func loadConfig() error {
_, err := os.Open("config.json")
if err != nil {
return errors.Wrap(err, "failed to load config")
}
return nil
}
func init() {
if err := loadConfig(); err != nil {
// Print stack trace
fmt.Printf("%+v\n", err)
}
}
```
## Error Logging
**Structured error logging:**
```go
import (
"log/slog"
)
func processOrder(orderID string) error {
order, err := fetchOrder(orderID)
if err != nil {
slog.Error("failed to fetch order",
"orderID", orderID,
"error", err,
)
return fmt.Errorf("fetch order %s: %w", orderID, err)
}
if err := validateOrder(order); err != nil {
slog.Warn("order validation failed",
"orderID", orderID,
"error", err,
)
return fmt.Errorf("validate order: %w", err)
}
return nil
}
```
## HTTP Error Handling
**Handling HTTP errors:**
```go
import (
"encoding/json"
"net/http"
)
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (e *APIError) Error() string {
return e.Message
}
func writeError(w http.ResponseWriter, err error) {
var apiErr *APIError
if errors.As(err, &apiErr) {
w.WriteHeader(apiErr.Code)
json.NewEncoder(w).Encode(apiErr)
return
}
// Default error
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(APIError{
Code: http.StatusInternalServerError,
Message: "Internal server error",
})
}
func handler(w http.ResponseWriter, r *http.Request) {
err := processRequest(r)
if err != nil {
writeError(w, err)
return
}
w.WriteHeader(http.StatusOK)
}
```
## Error Context
**Adding context to errors:**
```go
type ContextError struct {
Op string // Operation
Path string // File path, URL, etc.
Err error // Underlying error
}
func (e *ContextError) Error() string {
return fmt.Sprintf("%s %s: %v", e.Op, e.Path, e.Err)
}
func (e *ContextError) Unwrap() error {
return e.Err
}
func readFile(path string) error {
_, err := os.ReadFile(path)
if err != nil {
return &ContextError{
Op: "read",
Path: path,
Err: err,
}
}
return nil
}
```
## Testing Error Cases
**Testing error conditions:**
```go
package main
import (
"errors"
"testing"
)
func TestDivideByZero(t *testing.T) {
_, err := divide(10, 0)
if err == nil {
t.Fatal("expected error, got nil")
}
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.