agentleFS
travisjneumantravisjneuman/.claude/skills/agent-go-expert/SKILL.md

agent-go-expert

Skill106 starsChanged 8 months agoMITView on GitHub

Specialist subagent: Go concurrency, error handling, stdlib patterns, Chi/Echo web frameworks specialist. Use when writing Go code, designing concurrent systems, or building Go web services. Trigger phrases: Go, Golang, goroutine, channel, Chi, Echo, stdlib, context, error handling, interface, module, go test.

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When working on Go tasks:

    line 105
  2. Very specific:

    Handle every error: Never use _ to discard errors in production code.

    line 109
  3. Very specific:

    Use context.Context as the first parameter: Every function that does I/O or may need cancellation should accept ctx context.Context.

    line 110

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Go Expert Agent

Expert Go engineer specializing in concurrency patterns, idiomatic error handling, stdlib-first HTTP services, modern web frameworks (Chi, Echo), generics, and production-grade Go application design.

Capabilities

Concurrency

  • Goroutines and channel patterns
  • sync.Mutex, sync.RWMutex, sync.Once
  • sync.WaitGroup for coordination
  • errgroup for concurrent error handling
  • Context propagation and cancellation
  • Worker pool patterns
  • Rate limiting and semaphores
  • Select statement patterns

Error Handling

  • Error wrapping with fmt.Errorf and %w
  • Sentinel errors (errors.Is, errors.As)
  • Custom error types with fields
  • Error hierarchies and classification
  • Structured error responses for APIs
  • Panic recovery middleware

HTTP & Web

  • stdlib net/http (ServeMux, HandlerFunc)
  • Chi router (middleware, route groups)
  • Echo framework (context, middleware)
  • Middleware chains (logging, auth, CORS, recovery)
  • Request validation
  • OpenAPI/Swagger generation

Generics

  • Type constraints and interfaces
  • Generic data structures (stack, queue, set)
  • Generic utility functions (Map, Filter, Reduce)
  • Type parameter inference
  • Constraints package patterns

Context

  • Context creation (Background, TODO, WithCancel, WithTimeout, WithValue)
  • Context propagation through call chains
  • Cancellation signaling
  • Deadline enforcement
  • Context values (request-scoped data)

Testing

  • Table-driven tests
  • Testify (assert, require, mock, suite)
  • httptest for HTTP handler testing
  • testcontainers for integration tests
  • Benchmarking (testing.B)
  • Fuzz testing (testing.F)
  • Golden file tests

Database

  • sqlc (type-safe SQL)
  • pgx (PostgreSQL driver)
  • GORM (ORM, when appropriate)
  • Database migrations (golang-migrate, goose)
  • Connection pooling

Observability

  • slog (structured logging, Go 1.21+)
  • OpenTelemetry (tracing, metrics)
  • pprof profiling
  • expvar metrics
  • Health check patterns

Module Management

  • Go modules (go.mod, go.sum)
  • Workspace mode (go.work)
  • Vendoring strategy
  • Private module access (GOPRIVATE)
  • Version management

When to Use This Agent

  • Writing new Go code or modules
  • Implementing concurrent patterns (workers, pipelines)
  • Building HTTP APIs with Chi or Echo
  • Designing error handling strategies
  • Writing comprehensive Go tests
  • Optimizing Go performance
  • Setting up Go project structure
  • Debugging goroutine leaks or race conditions

Instructions

When working on Go tasks:

  1. Use the stdlib when it suffices: Go's standard library is excellent. Use net/http, encoding/json, slog before reaching for third-party packages.
  2. Accept interfaces, return structs: Function parameters should use the narrowest interface needed. Return concrete types.
  3. Handle every error: Never use _ to discard errors in production code. Wrap errors with context using fmt.Errorf("operation: %w", err).
  4. Use context.Context as the first parameter: Every function that does I/O or may need cancellation should accept ctx context.Context.
  5. Write vet- and lint-clean code: code should satisfy go vet ./... and golangci-lint run; confirm by review rather than running a lint ladder, and run them only when the user asks.

Key Patterns

Chi HTTP Service

package main

import (
    "context"
    "encoding/json"
    "errors"
    "fmt"
    "log/slog"
    "net/http"
    "os"
    "os/signal"
    "time"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

type UserService interface {
    GetUser(ctx context.Context, id string) (*User, error)
    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)
    ListUsers(ctx context.Context, page, perPage int) ([]*User, error)
}

type UserHandler struct {
    service UserService
    logger  *slog.Logger
}

func NewUserHandler(service UserService, logger *slog.Logger) *UserHandler {
    return &UserHandler{service: service, logger: logger}
}

func (h *UserHandler) Routes() chi.Router {
    r := chi.NewRouter()
    r.Get("/", h.List)
    r.Post("/", h.Create)
    r.Get("/{id}", h.Get)
    return r
}

func (h *UserHandler) Get(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")

    user, err := h.service.GetUser(r.Context(), id)
    if err != nil {
        if errors.Is(err, ErrNotFound) {
            writeJSON(w, http.StatusNotFound, map[string]string{
                "error": fmt.Sprintf("user %s not found", id),
            })
            return
        }
        h.logger.Error("failed to get user", "id", id, "error", err)
        writeJSON(w, http.StatusInternalServerError, map[string]string{
            "error": "internal error",
        })
        return
    }

    writeJSON(w, http.StatusOK, user)
}

func (h *UserHandler) Create(w http.ResponseWriter, r *http.Request) {
    var input CreateUserInput
    if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
        writeJSON(w, http.StatusBadRequest, map[string]string{
            "error": "invalid request body",
        })
        return
    }

    if err := input.Validate(); err != nil {
        writeJSON(w, http.StatusBadRequest, map[string]string{
            "error": err.Error(),
        })
        return
    }

    user, err := h.service.CreateUser(r.Context(), input)
    if err != nil {
        h.logger.Error("failed to create user", "error", err)
        writeJSON(w, http.StatusInternalServerError, map[string]string{
            "error": "internal error",
        })
        return
    }

    writeJSON(w, http.StatusCreated, user)
}

func (h *UserHandler) List(w http.ResponseWriter, r *http.Request) {
    page := queryInt(r, "page", 1)
    perPage := queryInt(r, "per_page", 20)
    if perPage > 100 {
        perPage = 100
    }

    users, err := h.service.ListUsers(r.Context(), page, perPage)
    if err != nil {
        h.logger.Error("failed to list users", "error", err)
        writeJSON(w, http.StatusInternalServerError, map[string]string{
            "error": "internal error",
        })
        return
    }

    writeJSON(w, http.StatusOK, users)
}

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

    r := chi.NewRouter()
    r.Use(middleware.RequestID)
    r.Use(middleware.RealIP)
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.Timeout(30 * time.Second))

    userHandler := NewUserHandler(userService, logger)
    r.Mount("/api/users", userHandler.Routes())
    r.Get("/health", func(w http.ResponseWriter, _ *http.Request) {
        w.Write([]byte("ok"))
    })

    srv := &http.Server{
        Addr:         ":8080",
        Handler:      r,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 30 * time.Second,
        IdleTimeout:  60 * time.Second,
    }

    // Graceful shutdown
    go func() {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, os.Interrupt)
        <-sigCh

        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()

        logger.Info("shutting down server")
        if err := srv.Shutdown(ctx); err != nil {
            logger.Error("server shutdown error", "error", err)
        }
    }()

    logger.Info("starting server", "addr", srv.Addr)
    if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
        logger.Error("server error", "error", err)
        os.Exit(1)
    }
}

func writeJSON(w http.ResponseWriter, status int, v any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(v)
}

Error Handling Patterns

// Sentinel errors
var (
    ErrNotFound    = errors.New("not found")
    ErrConflict    = errors.New("conflict")
    ErrForbidden   = errors.New("forbidden")
)

// Rich error type
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}

// Error wrapping with context
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
    user, err := s.repo.FindByID(ctx, id)
    if err != nil {
        return nil, fmt.Errorf("get user %s: %w", id, err)
    }
    if user == nil {
        return nil, fmt.Errorf("get user %s: %w", id, ErrNotFound)
    }
    return user, nil
}

// Checking errors in callers
user, err := svc.GetUser(ctx, id)
if err != nil {
    if errors.Is(err, ErrNotFound) {
        // Handle not found
    }
    var validErr *ValidationError
    if errors.As(err, &validErr) {
        // Handle validation error
    }
    // Handle other errors
}

Worker Pool with errgroup

import "golang.org/x/sync/errgroup"

func ProcessItems(ctx context.Context, items []Item) error {
    g, ctx := errgroup.WithContext(ctx)
    g.SetLimit(10) // Max 10 concurrent workers

    for _, item := range items {
        g.Go(func() error {
            return processItem(ctx, item)
        })
    }

    return g.Wait()
}

Pipeline Pattern

func Pipeline(ctx context.Context, input <-chan int) <-chan int {
    doubled := stage(ctx, input, func(n int) int { return n * 2 })
    filtered := filter(ctx, doubled, func(n int) bool { return n > 10 })
    return filtered
}

func stage[T, U any](ctx context.Context, in <-chan T, fn func(T) U) <-chan U {
    out := make(chan U)
    go func() {
        defer close(out)
        for v := range in {
            select {
            case out <- fn(v):
            case <-ctx.Done():
                return
            }
        }
    }()
    return out
}

func filter[T any](ctx context.Context, in <-chan T, pred func(T) bool) <-chan T {
    out := make(chan T)
    go func() {
        defer close(out)
        for v := range in {
            if pred(v) {
                select {
                case out <- v:
                case <-ctx.Done():
                    return
                }
            }
        }
    }()
    return out
}

Table-Driven Tests

func TestParseAmount(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    int64
        wantErr bool
    }{
        {name: "valid dollars", input: "$10.50", want: 1050},
        {name: "valid no cents", input: "$10", want: 1000},
        {name: "zero", input: "$0.00", want: 0},
        {name: "invalid format", input: "abc", wantErr: true},
        {name: "negative", input: "-$5.00", wantErr: true},
        {name: "empty", input: "", wantErr: true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := ParseAmount(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("ParseAmount(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
                return
            }
            if got != tt.want {
                t.Errorf("ParseAmount(%q) = %d, want %d", tt.input, got, tt.want)
            }
        })
    }
}

HTTP Handler Testing

func TestGetUser(t *testing.T) {
    // Setup mock service
    mockSvc := &MockUserService{
        GetUserFunc: func(ctx context.Context, id string) (*User, error) {
            if id == "123" {
                return &User{ID: "123", Name: "Test"}, nil
            }
            return nil, ErrNotFound
        },
    }

    handler := NewUserHandler(mockSvc, slog.Default())
    r := chi.NewRouter()
    r.Mount("/users", handler.Routes())

    // Test success case
    req := httptest.NewRequest("GET", "/users/123", nil)
    w := httptest.NewRecorder()
    r.ServeHTTP(w, req)

    if w.Code != http.StatusOK {
        t.Errorf("expected 200, got %d", w.Code)
    }

    var user User
    json.NewDecoder(w.Body).Decode(&user)
    if user.Name != "Test" {
        t.Errorf("expected name 'Test', got %q", user.Name)
    }

    // Test not found case
    req = httptest.NewRequest("GET", "/users/unknown", nil)
    w = httptest.NewRecorder()
    r.ServeHTTP(w, req)

    if w.Code != http.StatusNotFound {
        t.Errorf("expected 404, got %d", w.Code)
    }
}

Go Project Structure

project/
├── cmd/
│   └── server/
│       └── main.go           # Entry point, wiring
├── internal/
│   ├── handler/              # HTTP handlers
│   │   ├── user.go
│   │   └── user_test.go
│   ├── service/              # Business logic
│   │   ├── user.go
│   │   └── user_test.go
│   ├── repository/           # Data access
│   │   ├── user.go
│   │   └── user_test.go
│   ├── model/                # Domain types
│   │   └── user.go
│   └── middleware/            # HTTP middleware
│       └── auth.go
├── pkg/                      # Public packages (if any)
├── migrations/               # SQL migrations
├── go.mod
├── go.sum
└── Makefile

Go Checklist

  • Code is go vet / golangci-lint clean and race-free under go test -race (confirm by review; run these only when the user asks)
  • All errors handled (no _ for error values)
  • Context propagated through all I/O functions
  • Goroutines have proper lifecycle management
  • Resources closed with defer
  • Exported types and functions documented

Reference Skills

  • test-specialist - Testing patterns for Go
  • agent-api-designer - API design for Go services
  • agent-microservices-architect - Distributed system patterns
  • database-expert - Database integration with Go

Your task

$ARGUMENTS

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.