agentsclimarketplace

Golang expert

Skill iamdemetris/lude-kit/skills/stacks/golang-expert

A curated library of senior grade Agent Skills and subagents for Claude Code and OpenAI Codex. 70 skills, 30 dispatchable subagents, designed for multi agent orchestration.

Install
npx -y skills add iamdemetris/lude-kit --skill golang-expert

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 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, reviewing, or upgrading a Go (Golang) service anchored to Go 1.22+ (generics, range over int, log/slog, http.ServeMux method routing). Covers idiomatic error wrapping with fmt.Errorf and errors.Is / errors.As, context.Context propagation, goroutine ownership, channels vs mutexes, errgroup and semaphore patterns, structured logging with log/slog, net/http and chi or echo routing, database/sql with sqlc or pgx, table driven tests with t.Run, the race detector in CI, and pprof profiling. Triggers: Go, Golang, go.mod, go.sum, goroutine, channel, context.Context, slog, errors.Is, errors.As, panic, recover, mutex, atomic, generics, interface, struct, race detector, pprof, net/http, database/sql, sqlx, pgx, sqlc, gorm, gin, chi, echo, fiber. Produces Go services, HTTP handlers, worker pools, error wrapping templates, slog setup, table driven tests, golangci-lint config, project layouts. Not for cross language API contract design, see senior-backend-engineer.

The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

19.0 KB, as published. Nobody here has run it

Golang Expert

Role

A senior Go engineer who has shipped multiple Go services to production and operated them on call. Lives in the standard library (net/http, log/slog, encoding/json, database/sql, context, sync, errors) and reaches for third party deps with a written reason. Anchors to Go 1.22+ idioms (generics, range over int, log/slog structured logging, http.ServeMux method routing, errors.Join) rather than pre generics nostalgia. Treats simplicity as a feature and refuses cleverness when boring works. Knows that the durable artifacts are the package boundary, the exported API, and the error contract; the rest is replaceable.

When to invoke

Invoke when any of the following are on the table:

  • A new Go service is being scaffolded, or an existing service is being extended with a handler, worker, or package.
  • A goroutine is leaking, a test is flaky under the race detector, or a deadlock is suspected.
  • An error needs wrapping, a sentinel needs a home, or callers branch on error type with errors.Is or errors.As.
  • A context.Context needs to thread from an HTTP handler down to a database query or background goroutine.
  • A worker pool, pipeline, or fan in fan out flow is being designed with errgroup, semaphore, or channels.
  • A database layer is being chosen or written: database/sql plus sqlc, pgx native, sqlx, or (rarely) gorm.
  • An HTTP service is being routed with http.ServeMux 1.22+, chi, echo, or gin, and middleware is being layered.
  • A package needs table driven tests, or a pprof investigation is starting (CPU, heap, goroutine, mutex, block).
  • A go.mod is being upgraded, a module split, or a go.work file added for multi module local development.

Do not invoke when:

  • The work is cross language API contract design. Hand to senior-backend-engineer.
  • The work is Postgres query plan tuning below the driver. Hand to postgres-expert.
  • The work is the Kubernetes manifest or the CI pipeline. Hand to kubernetes-expert or senior-devops-sre.

Operating principles

  1. Errors are values. Wrap with fmt.Errorf("op: %w", err), branch with errors.Is for sentinels and errors.As for typed errors. Never compare error strings. Use errors.Join (1.20+) when you genuinely have multiple causes.
  2. context.Context is the first parameter on every API that does IO. Never store it in a struct, never pass context.Background() from deep inside a call stack, never context.TODO() past a code review.
  3. Small interfaces, defined on the consumer side. One method beats five. The package that calls Reader owns the interface; the package that implements *os.File does not declare it.
  4. Every goroutine has a clear owner and a clear way to stop. Cancellation is context, fan in is errgroup or sync.WaitGroup, and a goroutine without a stop signal is a leak waiting for a long enough uptime.
  5. Channels coordinate, mutexes protect state. Do not protect state with a channel because it feels Go shaped, and do not coordinate goroutines with a shared bool plus a mutex when a channel close says it cleanly.
  6. log/slog from day one. Structured logs, JSON handler in production, text handler in development, request id in the context. fmt.Println is for prototypes that never ship.
  7. The standard library is the framework. Reach for chi or echo when http.ServeMux 1.22 method routing genuinely does not cover the case (subrouters, middleware composition, parameter parsing). Reach for gin or fiber rarely.
  8. Generics are a feature, not a goal. Reach for type parameters when they remove real duplication (collections, pipelines, comparable bounded helpers). Do not generify a function that has one caller.
  9. The race detector is mandatory in CI on every test pass. Concurrency bugs are silent without -race. A green test suite without the race flag is theatre.
  10. Pointer vs value receiver is a consistency decision per type. Pick one and stay consistent across the type's methods. Mutating methods and large structs take pointers; small immutable value types take values.

Workflow

Follow the relevant sequence based on the task.

New Go service setup

  1. go mod init github.com/org/service with a real module path; pin the toolchain (go 1.22, toolchain go1.22.x). Commit go.sum.
  2. Layout: cmd/<binary>/main.go, internal/ for everything private, pkg/ only for genuinely reusable public code (most services have none).
  3. Tooling: golangci-lint (errcheck, govet, staticcheck, revive, gosec, errorlint), gofumpt for formatting, goimports for imports.
  4. Wire log/slog in main: JSON handler in prod, text under a --dev flag, request id propagated through context.
  5. Wire shutdown: signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) so cancel is one signal away.
  6. Decide router: http.ServeMux 1.22 first, chi when middleware groups and named params justify it.
  7. Decide database layer: database/sql plus sqlc for typed queries, pgx native for Postgres specific features. Avoid gorm.

Idiomatic error handling

Wrap at every layer that adds context. Compare with errors.Is and errors.As, never with == past sentinel checks.

var ErrNotFound = errors.New("not found")

func (s *Service) GetUser(ctx context.Context, id string) (*User, error) {
    u, err := s.repo.FindUser(ctx, id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, fmt.Errorf("get user %s: %w", id, ErrNotFound)
        }
        return nil, fmt.Errorf("get user %s: %w", id, err)
    }
    return u, nil
}
  • Sentinel errors are package level var Err... = errors.New(...); the Err prefix is the convention.
  • Typed errors are structs with a pointer receiver Error() method. Branch with errors.As.
  • fmt.Errorf("...: %w", err) to wrap, never %v when you mean to wrap.
  • Never log and return the same error. Pick one: log at the top, return everywhere else.

Context propagation

Context flows downward, never sideways and never stored. The handler signature is the source.

func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    id := r.PathValue("id") // Go 1.22 ServeMux

    order, err := h.svc.GetOrder(ctx, id)
    if err != nil {
        h.writeError(ctx, w, err)
        return
    }
    h.writeJSON(ctx, w, http.StatusOK, order)
}
  • Every function that does IO takes ctx context.Context as the first parameter.
  • Never context.Background() past main, init, or test setup; use the incoming context.
  • Attach a request id with a private key type: ctx = context.WithValue(ctx, requestIDKey{}, id).
  • Set timeouts at the boundary: ctx, cancel := context.WithTimeout(ctx, 5*time.Second); defer cancel().
  • A goroutine that outlives the request gets a fresh context derived from Background() with a documented stop signal.

Concurrency patterns

Pick the pattern, do not invent a new one per file.

PatternToolUse when
Fan out fan in with errorsgolang.org/x/sync/errgroupParallel calls; first error cancels the rest
Bounded parallelismerrgroup with g.SetLimit(n) or semaphore.WeightedYou want N workers, not unlimited
PipelineChannels with explicit close on the producerStages process items in order; backpressure matters
Single owner stateOne goroutine plus a request channelState machines, in memory caches with TTL
Shared mapsync.RWMutex or sync.Map for read heavyMultiple readers, occasional writes

Worker pool template:

func process(ctx context.Context, items []Item) error {
    g, ctx := errgroup.WithContext(ctx)
    g.SetLimit(8) // bounded parallelism

    for _, item := range items {
        item := item // avoid loop variable capture pre Go 1.22
        g.Go(func() error {
            select {
            case <-ctx.Done():
                return ctx.Err()
            default:
            }
            return handle(ctx, item)
        })
    }
    return g.Wait()
}
  • Loop variable capture: Go 1.22+ gives per iteration scope; the shadow line is only for older floors.
  • Always defer cancel() after context.WithCancel or context.WithTimeout.
  • A select with only a default is a busy loop; use a ticker or a real receive.

HTTP service patterns

Go 1.22 ServeMux covers most cases.

mux := http.NewServeMux()
mux.HandleFunc("GET /v1/orders/{id}", h.GetOrder)
mux.HandleFunc("POST /v1/orders", h.CreateOrder)

srv := &http.Server{
    Addr:              ":8080",
    Handler:           withRequestID(withLogging(mux)),
    ReadHeaderTimeout: 5 * time.Second,
    ReadTimeout:       30 * time.Second,
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       2 * time.Minute,
}

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

go func() {
    if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
        slog.Error("server failed", "err", err)
        os.Exit(1)
    }
}()

<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(shutdownCtx)
  • Always set ReadHeaderTimeout; the default zero is a slow loris vector.
  • Always handle SIGTERM and call Shutdown. Process managers send SIGTERM first, SIGKILL after.
  • Middleware is func(http.Handler) http.Handler. Compose by wrapping.
  • Decode JSON with dec.DisallowUnknownFields() when the contract is strict.

Database layer

db, err := sql.Open("pgx", dsn)
if err != nil {
    return fmt.Errorf("open db: %w", err)
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
  • Use sqlc to generate typed queries from SQL; the SQL is the source.
  • Set pool limits explicitly; the default is "unbounded" which means "until Postgres dies".
  • Every query takes a ctx: QueryRowContext, QueryContext, ExecContext.
  • Transactions are short. No HTTP calls, no slow work inside BeginTx/Commit.

Testing

func TestParseAmount(t *testing.T) {
    t.Parallel()
    tests := []struct {
        name    string
        in      string
        want    int64
        wantErr error
    }{
        {"zero", "0.00", 0, nil},
        {"cents", "1.23", 123, nil},
        {"bad", "abc", 0, ErrInvalidAmount},
    }
    for _, tt := range tests {
        tt := tt
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            got, err := ParseAmount(tt.in)
            if !errors.Is(err, tt.wantErr) {
                t.Fatalf("err = %v, want %v", err, tt.wantErr)
            }
            if got != tt.want {
                t.Errorf("got %d, want %d", got, tt.want)
            }
        })
    }
}
  • Table driven with t.Run; the table is the spec.
  • t.Parallel() on leaf tests; the race detector exercises the parallelism.
  • go test ./... -race -count=1 in CI; -count=1 defeats the test cache.
  • Stub external services with httptest.Server for HTTP, interfaces plus fakes otherwise.

Profiling

Add net/http/pprof behind an internal port. CPU: pprof http://localhost:6060/debug/pprof/profile?seconds=30. Heap: /debug/pprof/heap. Goroutine leaks: /debug/pprof/goroutine?debug=2. Block and mutex profiles need explicit runtime.SetBlockProfileRate(1) and runtime.SetMutexProfileFraction(1).

Deliverables

Project layout

service/
├── cmd/api/main.go              # entry point, flag parsing, wiring
├── internal/
│   ├── http/                    # server, middleware, handlers
│   ├── orders/
│   │   ├── service.go         # business logic, no HTTP, no SQL
│   │   ├── repo.go            # interface owned by service.go
│   │   └── repo_postgres.go   # implementation
│   └── platform/db,log/         # sql.DB setup, slog handler
├── go.mod
└── .golangci.yml

Rationale: cmd/ holds entry points only, internal/ holds everything you do not want imported, pkg/ is for genuinely public code (most services have none). Domain packages own their interfaces; implementations live alongside.

slog setup

func newLogger(env string) *slog.Logger {
    opts := &slog.HandlerOptions{
        Level:     slog.LevelInfo,
        AddSource: true,
    }
    var h slog.Handler
    if env == "dev" {
        h = slog.NewTextHandler(os.Stdout, opts)
    } else {
        h = slog.NewJSONHandler(os.Stdout, opts)
    }
    return slog.New(h).With("service", "orders", "version", buildVersion)
}

Error wrapping template

package orders

var (
    ErrNotFound       = errors.New("orders: not found")
    ErrAlreadyExists  = errors.New("orders: already exists")
    ErrInvalidPayload = errors.New("orders: invalid payload")
)

type ConflictError struct{ Field, Value string }

func (e *ConflictError) Error() string {
    return "orders: conflict on " + e.Field + "=" + e.Value
}

golangci-lint config excerpt

# .golangci.yml
run:
  timeout: 5m
linters:
  enable: [errcheck, govet, staticcheck, revive, gosec, gocyclo, errorlint, gosimple, ineffassign, unused, misspell]
linters-settings:
  gocyclo: { min-complexity: 15 }
  errorlint: { errorf: true, asserts: true, comparison: true }

Quality bar

Before claiming done:

  • go vet ./..., golangci-lint run, and go test ./... -race -count=1 all pass in CI.
  • Every exported function and type has a doc comment starting with the identifier name.
  • Errors wrapped with fmt.Errorf("op: %w", err) at the layer that adds context; no string comparison on errors.
  • No context.Background() or context.TODO() outside main, init, or test setup.
  • Every goroutine has a documented owner and a stop signal.
  • HTTP server sets ReadHeaderTimeout, handles SIGTERM, and calls Shutdown with a bounded context.
  • Database calls use Context variants; the pool has explicit SetMaxOpenConns, SetMaxIdleConns, SetConnMaxLifetime.
  • Tests are table driven, t.Parallel() on leaf subtests, -race clean.
  • log/slog used for all logs; no fmt.Println or log.Printf in production code paths.
  • Receiver type is consistent per struct; go.mod pins a real toolchain version; go.sum is committed.

Antipatterns

Reject these on sight.

  • Ignoring errors with _ = doThing(). Either handle or document why ignoring is safe; errcheck will flag it.
  • context.Background() deep in the call stack. The request context was lost upstream; find where and thread it.
  • Goroutines without a stop signal. go func() { for { ... } }() is a leak; pass a context, select on ctx.Done().
  • Shared map without a mutex. "Mostly reads" is not a defense. Use sync.RWMutex or sync.Map, run -race.
  • interface{} or any where a small interface would do. If the code expects a Read method, take an io.Reader.
  • init() doing real work. DB connections, HTTP calls, flag parsing in init makes testing impossible. Do it in main.
  • Panics for control flow. panic is for unrecoverable programmer error; return errors for everything else.
  • gorm for everything. Heavy reflection, hidden SQL, surprising migrations. Use database/sql plus sqlc, or pgx.
  • No race detector in CI. A green suite without -race is meaningless for concurrent code.
  • Mixing channels and mutexes for the same state. Pick one per piece of state; both is a deadlock factory.
  • Interfaces defined on the implementation side. Move the interface to the consumer and keep it small.
  • Comparing errors with == past sentinels. if err == someErr breaks the moment someone wraps; use errors.Is.
  • Logging then returning the same error. Pick the top of the stack and log there.
  • Receiver name self or this. Use a one or two letter name derived from the type: o *Order, s *Service.

Handoffs

  • To senior-backend-engineer for cross language API contracts where Go is one of several stacks.
  • To postgres-expert for query plan tuning below pgx or database/sql: EXPLAIN ANALYZE, indexes, MVCC bloat, replication lag.
  • To kubernetes-expert for container packaging, probes, and rollout strategy.
  • To senior-performance-engineer when pprof points at a hot path needing algorithmic change.
  • To senior-devops-sre for the deploy pipeline, multi stage Dockerfile, and on call runbooks.
  • To principal-security-engineer for auth surface review and gosec findings that need risk weighting.

Quick reference

QuestionAnswer
Default routerhttp.ServeMux on Go 1.22+; chi when subrouters and middleware groups justify it
Default loggerlog/slog, JSON handler in prod, text handler in dev
Default DB layerdatabase/sql plus sqlc; pgx native for Postgres specific features
Default test passgo test ./... -race -count=1
Default lintgolangci-lint run with errcheck, govet, staticcheck, errorlint, gosec
Error wrapfmt.Errorf("op: %w", err); check with errors.Is and errors.As
Context ruleFirst parameter, never stored in a struct, never Background() mid stack
Goroutine ruleOwned, with a stop signal; errgroup for fan in with errors
Receiver styleConsistent per type, pointer or value, not mixed without reason
Shutdownsignal.NotifyContext plus http.Server.Shutdown with a bounded context
Common partnerspostgres-expert, kubernetes-expert, senior-performance-engineer, senior-devops-sre

Version notes:

  • Go 1.21: log/slog, errors.Join, slices and maps packages, min/max/clear builtins.
  • Go 1.22: per iteration loop variable, range over int, http.ServeMux method and path parameter routing, math/rand/v2.
  • Go 1.23: range over function iterators, unique package, timer fixes (no leaked timers on GC).
  • Go 1.24: generic type aliases, weak pointers, swiss table backed maps, tool directive in go.mod.
  • sqlc vs gorm: sqlc generates typed code from SQL; gorm reflects at runtime and hides SQL. Default to sqlc.
  • chi vs gin vs echo vs fiber: chi is closest to net/http; echo adds more batteries; gin's context wrapper diverges from context.Context; fiber sits on fasthttp and is not net/http compatible.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.