Skip to main content
skill Claude Code CX Codex Cursor GitHub Copilot

golang-samber-oops — Claude Code skill: structured errors in Go

Skill adds support for samber/oops to Claude Code, Cursor and other IDEs — a Go library that brings structured error handling with context, stack traces, error codes, and user-facing messages.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add samber/cc-skills-golang --skill golang-samber-oops -a claude-code
Open on GitHub

What golang-samber-oops does

samber/oops replaces Go's standard error handling with structured data. Instead of fmt.Errorf("connection failed"), you build an error object: set the domain (service name), tags, error code, user context, and pass variable data via .With() attributes, not the message string. All IDs, request values, and SQL queries go into attributes. This lets APM tools (Datadog, Sentry, Loki) group errors by type, not by unique values.

The skill teaches your agent to chain methods: .In("domain"), .Tags(...), .Code("error_slug"), .User(id, key, value), .With(key, value), .Public("user message") and a terminal method like .Errorf() or .Wrapf(err, format, args). It covers database error handling, HTTP handlers, service layers, error wrapping, and panic recovery.

You need Go (the go tool in PATH) and knowledge of where errors occur in your code — the skill will suggest which attributes to add and what error code fits. If your project already imports github.com/samber/oops, the skill finds it and proposes using it.

What it helps with

  • add structured context to errors without changing their format
  • separate technical error messages from user-facing messages
  • set up error logging and tracing with codes and attributes
  • capture call stacks and request details when errors occur
  • make error grouping in APM work by type, not by concrete values

Example prompts for the agent

  • Help me handle a database connection error with user context and the SQL query
  • Add structured errors to the POST /users handler with a public message
  • Wrap an existing error in a service using oops with a code and tags

How to install golang-samber-oops

1Skills CLI (Vercel)

npx skills add samber/cc-skills-golang --skill golang-samber-oops -a claude-code

2Manually: put the files in place

~/.claude/skills/golang-samber-oops/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/golang-samber-oops/

From the same set32

The whole set →
golang-code-style samber A skill for writing clear Go code: line breaking, variable declarations, control flow, when comments help. Use it when writing or reviewing Go, discussing style, or setting project standards. Claude Code CX Codex Cursor +1 44,000installs golang-error-handling samber Skill for idiomatic Go error handling: wrapping with context, chain inspection, structured logging, and samber/oops integration. Apply when creating, wrapping, inspecting, or logging errors in Go code. Claude Code CX Codex Cursor +1 43,200installs golang-security samber Identifies Go vulnerabilities, models threats with STRIDE and DREAD, and fixes injections, secret leaks, and cryptography flaws. Claude Code CX Codex Cursor +1 42,100installs golang-design-patterns samber This skill guides idiomatic Go design for APIs, resource lifecycles, graceful shutdown, and architecture. Use it when designing or reviewing Go code. Claude Code CX Codex Cursor +1 41,900installs golang-performance samber Guides AI coding agents through profiling, memory allocation cuts, and CPU optimization in Go projects using a strict measurement-first cycle. Claude Code CX Codex Cursor +1 41,800installs golang-concurrency samber The skill sets rules for concurrent Go code: goroutine lifecycle, channels and select, mutexes, errgroup, singleflight, worker pools, and fan-out/fan-in pipelines. Claude Code CX Codex Cursor +1 41,600installs golang-database samber Go database guidelines for database/sql, sqlx, and pgx. Enforces parameterized queries, context passing, and explicit connection pool handling without ORMs. Claude Code CX Codex Cursor +1 40,700installs golang-naming samber A Go naming skill for writing, reviewing, and refactoring code. It covers packages, types, errors, booleans, enums, and tests. Claude Code CX Codex Cursor +1 40,700installs

Similar

iii-engine-config iii-hq Configures the iii engine via worker-compose.yaml or config.yaml. Sets up engine ports, observers, RBAC proxy, and configuration storage. Claude Code CX Codex Cursor +1 4,200installs stripe-integration Seth Hobson This skill covers Stripe Checkout, subscriptions, webhooks, refunds, and saved payment methods. Use it when a project needs one-time or recurring payments. Claude Code CX Codex Cursor 13,404installs mcp-builder official anthropics This skill walks through building an MCP server in TypeScript or Python, from reading the protocol spec to writing tests and evaluation questions. Claude Code Claude.ai 130,949installs google-agents-cli-adk-code official Google A reference skill for building ADK agents with tools, callbacks, state, and workflows. Use it when an agent project needs concrete API patterns or code examples. Claude Code CX Codex Cursor +1 511,400installs lark-im larksuite This skill lets an agent work with Lark IM messages, chats, files, and interactive cards. Use it when coding tasks need access to team conversations. Claude Code CX Codex Cursor +1 472,300installs lark-event larksuite Listens to Lark/Feishu events and streams them as NDJSON. Use it for bots, message processing, and long-running subscribers. Claude Code CX Codex Cursor +1 466,200installs caveman-discover JuliusBrussee Finds and labels every LLM workflow in your repository so Caveman Cloud breaks down spend by actual job rather than grouping everything into one bucket. Claude Code CX Codex Cursor +1 103,500installs migration JuliusBrussee Enforces reversible, compatibility-safe transitions for schema, data, and API updates. Useful whenever data loss or rollout downtime is unacceptable. Claude Code CX Codex Cursor +1 102,900installs

Questions about golang-samber-oops

How do I install golang-samber-oops in Claude Code?

Run npx skills add samber/cc-skills-golang --skill golang-samber-oops -a claude-code, then check ~/.claude/skills/golang-samber-oops/SKILL.md to verify.

What does golang-samber-oops do?

The skill teaches your agent to use the samber/oops library for structured error handling in Go: add context, attributes, error codes, user messages, and stack traces via method chaining — .In(), .Tags(), .Code(), .With(), .Public() ending with .Errorf() or .Wrapf().

What do I need before using this skill?

You need Go (the go command in PATH). If your project already imports github.com/samber/oops, the skill will find it; otherwise, the agent will suggest adding it via go get.

Source text

SKILL.md · MIT
---
name: golang-samber-oops
description: "Structured error handling in Golang with samber/oops — error builders, stack traces, error codes, error context, error wrapping, error attributes, user-facing vs developer messages, panic recovery, and logger integration. Apply when using or adopting samber/oops, or when the codebase already imports github.com/samber/oops."
user-invocable: true
license: MIT
compatibility: Designed for Claude Code, Codex or similar harness, and for projects using Golang.
metadata:
  author: samber
  version: "1.2.1"
  openclaw:
    emoji: "💥"
    homepage: https://github.com/samber/cc-skills-golang
    requires:
      bins:
        - go
    install: []
    skill-library-version: "1.21.0"
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*) Bash(git:*) Agent WebFetch mcp__context7__resolve-library-id mcp__context7__query-docs Bash(godig:*) Bash(gopls:*) LSP mcp__gopls__*
paths:
  - "**/*.go"
---

**Persona:** You are a Go engineer who treats errors as structured data. Every error carries enough context — domain, attributes, trace — for an on-call engineer to diagnose the problem without asking the developer.

# samber/oops Structured Error Handling

**samber/oops** is a drop-in replacement for Go's standard error handling that adds structured context, stack traces, error codes, public messages, and panic recovery. Variable data goes in `.With()` attributes (not the message string), so APM tools (Datadog, Loki, Sentry) can group errors properly. Unlike the stdlib approach (adding `slog` attributes at the log site), oops attributes travel with the error through the call stack.

## Why use samber/oops

Standard Go errors lack context — you see `connection failed` but not which user triggered it, what query was running, or the full call stack. `samber/oops` provides:

- **Structured context** — key-value attributes on any error
- **Stack traces** — automatic call stack capture
- **Error codes** — machine-readable identifiers
- **Public messages** — user-safe messages separate from technical details
- **Low-cardinality messages** — variable data in `.With()` attributes, not the message string, so APM tools group errors properly

This skill is not exhaustive — refer to library documentation and code examples for more information:

- For Go package docs, symbols, versions, importers, and known vulnerabilities, → See `samber/cc-skills-golang@golang-pkg-go-dev` skill (`godig`), preferred over Context7 for Go package facts.
- To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See `samber/cc-skills-golang@golang-gopls` skill (`gopls`).
- Context7 remains a fallback for docs not indexed on pkg.go.dev.

## Core pattern: Error builder chain

All `oops` errors use a fluent builder pattern:

```go
err := oops.
    In("user-service").           // domain/feature
    Tags("database", "postgres").  // categorization
    Code("network_failure").       // machine-readable identifier
    User("user-123", "email", "[email protected]").  // user context
    With("query", query).          // custom attributes
    Errorf("failed to fetch user: %s", "timeout")
```

Terminal methods:

- `.Errorf(format, args...)` — create a new error
- `.Wrap(err)` — wrap an existing error
- `.Wrapf(err, format, args...)` — wrap with a message
- `.Join(err1, err2, ...)` — combine multiple errors
- `.Recover(fn)` / `.Recoverf(fn, format, args...)` — convert panic to error

### Error builder methods

| Methods | Use case |
| --- | --- |
| `.With("key", value)` | Add custom key-value attribute (lazy `func() any` values supported) |
| `.WithContext(ctx, "key1", "key2")` | Extract values from Go context into attributes (lazy values supported) |
| `.In("domain")` | Set the feature/service/domain |
| `.Tags("auth", "sql")` | Add categorization tags (query with `err.HasTag("tag")`) |
| `.Code("iam_authz_missing_permission")` | Set machine-readable error identifier/slug |
| `.Public("Could not fetch user.")` | Set user-safe message (separate from technical details) |
| `.Hint("Runbook: https://doc.acme.org/doc/abcd.md")` | Add debugging hint for developers |
| `.Owner("team/slack")` | Identify responsible team/owner |
| `.User(id, "k", "v")` | Add user identifier and attributes |
| `.Tenant(id, "k", "v")` | Add tenant/organization context and attributes |
| `.Trace(id)` | Add trace / correlation ID (default: ULID) |
| `.Span(id)` | Add span ID representing a unit of work/operation (default: ULID) |
| `.Time(t)` | Override error timestamp (default: `time.Now()`) |
| `.Since(t)` | Set duration based on time since `t` (exposed via `err.Duration()`) |
| `.Duration(d)` | Set explicit error duration |
| `.Request(req, includeBody)` | Attach `*http.Request` (optionally including body) |
| `.Response(res, includeBody)` | Attach `*http.Response` (optionally including body) |
| `oops.FromContext(ctx)` | Start from an `OopsErrorBuilder` stored in a Go context |

## Common scenarios

### Database/repository layer

```go
func (r *UserRepository) FetchUser(id string) (*User, error) {
    query := "SELECT * FROM users WHERE id = $1"
    row, err := r.db.Query(query, id)
    if err != nil {
        return nil, oops.
            In("user-repository").
            Tags("database", "postgres").
            With("query", query).
            With("user_id", id).
            Wrapf(err, "failed to fetch user from database")
    }
    // ...
}
```

### HTTP handler layer

```go
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
    userID := getUserID(r)

    err := h.service.CreateUser(r.Context(), userID)
    if err != nil {
        err = oops.
            In("http-handler").
            Tags("endpoint", "/users").
            Request(r, false).
            User(userID).
            Wrapf(err, "create user failed")
        http.Error(w, oops.GetPublic(err, "Internal server error"), http.StatusInternalServerError)
        return
    }

    w.WriteHeader(http.StatusCreated)
}
```

### Service layer with reusable builder

```go
func (s *UserService) CreateOrder(ctx context.Context, req CreateOrderRequest) error {
    builder := oops.
        In("order-service").
        Tags("orders", "checkout").
        Tenant(req.TenantID, "plan", req.Plan).
        User(req.UserID, "email", req.UserEmail)

    product, err := s.catalog.GetProduct(ctx, req.ProductID)
    if err != nil {
        return builder.
            With("product_id", req.ProductID).
            Wrapf(err, "product lookup failed")
    }

    if product.Stock < req.Quantity {
        return builder.
            Code("insufficient_stock").
            Public("Not enough items in stock.").
            With("requested", req.Quantity).
            With("available", product.Stock).
            Errorf("insufficient stock for product %s", req.ProductID)
    }

    return nil
}
```

## Error wrapping best practices

### DO: Wrap directly, no nil check needed

```go
// ✓ Good — Wrap returns nil if err is nil
return oops.Wrapf(err, "operation failed")

// ✗ Bad — unnecessary nil check
if err != nil {
    return oops.Wrapf(err, "operation failed")
}
return nil
```

### DO: Add context at each layer

Each architectural layer SHOULD add context via Wrap/Wrapf — at least once per package boundary (not necessarily at every function call).

```go
// ✓ Good — each layer adds relevant context
func Controller() error {
    return oops.In("controller").Trace(traceID).Wrapf(Service(), "user request failed")
}

func Service() error {
    return oops.In("service").With("op", "create_user").Wrapf(Repository(), "db operation failed")
}

func Repository() error {
    return oops.In("repository").Tags("database", "postgres").Errorf("connection timeout")
}
```

### DO: Keep error messages low-cardinality

Error messages MUST be low-cardinality for APM aggregation. Interpolating variable data into the message breaks grouping in Datadog, Loki, Sentry.

```go
// ✗ Bad — high-cardinality, breaks APM grouping
oops.Errorf("failed to process user %s in tenant %s", userID, tenantID)

// ✓ Good — static message + structured attributes
oops.With("user_id", userID).With("tenant_id", tenantID).Errorf("failed to process user")
```

## Panic recovery

`oops.Recover()` MUST be used in goroutine boundaries. Convert panics to structured errors:

```go
func ProcessData(data string) (err error) {
    return oops.
        In("data-processor").
        Code("panic_recovered").
        Hint("Check input data format and dependencies").
        With("input_data", data).
        Recover(func() {
            riskyOperation(data)
        })
}
```

## Accessing error information

`samber/oops` errors implement the standard `error` interface. Access additional info:

```go
if oopsErr, ok := err.(oops.OopsError); ok {
    fmt.Println("Code:", oopsErr.Code())
    fmt.Println("Domain:", oopsErr.Domain())
    fmt.Println("Tags:", oopsErr.Tags())
    fmt.Println("Context:", oopsErr.Context())
    fmt.Println("Stacktrace:", oopsErr.Stacktrace())
}

// Get public-facing message with fallback
publicMsg := oops.GetPublic(err, "Something went wrong")
```

### Output formats

```go
fmt.Printf("%+v\n", err)       // verbose with stack trace
bytes, _ := json.Marshal(err)  // JSON for logging
slog.Error(err.Error(), slog.Any("error", err))  // slog integration
```

## Context propagation

Carry error context through Go contexts:

```go
func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        builder := oops.
            In("http").
            Request(r, false).
            Trace(r.Header.Get("X-Trace-ID"))

        ctx := oops.WithBuilder(r.Context(), builder)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func handler(ctx context.Context) error {
    return oops.FromContext(ctx).Tags("handler", "users").Errorf("something failed")
}
```

For assertions, configuration, and additional logger examples, see [Advanced patterns](./references/advanced.md).

## References

- [github.com/samber/oops](https://github.com/samber/oops)
- [pkg.go.dev/github.com/samber/oops](https://pkg.go.dev/github.com/samber/oops)

## Cross-References

- → See `samber/cc-skills-golang@golang-error-handling` skill for general error handling patterns
- → See `samber/cc-skills-golang@golang-observability` skill for logger integration and structured logging

© samber · MIT · samber/cc-skills-golang

See also

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor