# Gonzaloserrano Gopilot Gopilot

> Go Engineering

- Skill: `tomevault-io/gonzaloserrano-gopilot-gopilot` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/gonzaloserrano-gopilot-gopilot`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/gonzaloserrano-gopilot-gopilot/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/gonzaloserrano-gopilot-gopilot

---


# Go Engineering

## Reference files

This SKILL.md is a SUMMARY. The files under `reference/` carry stricter rules
NOT all reproduced here. If your task touches a topic below, the reference is
required reading — don't trust the abbreviated version on this page.

- `reference/go-testing.md` — table-driven tests, testify, subtests, helpers
- `reference/go-aip.md` — Google AIP and einride/aip-go for resource-oriented gRPC
- `reference/error-handling.md` — security-sensitive error patterns
- `reference/logging.md` — structured `slog`, secret redaction, correlation IDs
- `reference/linting.md` — golangci-lint configuration
- `reference/security-checklist.md` — OWASP Go pre-deploy checklist
- `reference/access-control.md`, `reference/authentication.md`, `reference/cryptography.md`, `reference/csrf.md`, `reference/database-security.md`, `reference/file-security.md`, `reference/input-validation.md`, `reference/session-management.md`, `reference/tls-https.md`, `reference/xss.md` — security topics (OWASP)

## API Inspection

Before guessing function signatures or fabricating package APIs, inspect the source:

- `go doc fmt.Errorf` — package or symbol docs (stdlib + module deps)
- `go doc -src strings.Cut` — show source
- `go doc ./internal/foo` — local package
- `gopls definition <file>:<line>:<col>` — jump to definition
- `gopls references <file>:<line>:<col>` — find call sites
- `gopls symbols <file>` — list package symbols

Use these over inferring APIs. Hallucinated signatures fail at compile time; `go doc` is faster than the build-fail loop.

## Design Guidelines

- Keep things simple.
   - Prefer stateless, pure functions over stateful structs with methods if no state is needed to solve the problem. 
   - Prefer synchronous code to concurrent code, an obvious concurrency pattern applies.
   - Prefer simple APIs and small interfaces.
   - Make the zero value useful: `bytes.Buffer` and `sync.Mutex` work without init. When zero values compose, there's less API.
   - Avoid external dependencies. A little copying is better than a little dependency.
- Clear is better than clever. Maintainability and readibility are important.
- Don't just check errors, handle them gracefully.
- Design the architecture, name the components, document the details: Good names carry the weight of expressing your design. If names are good, the design is clear on the page.
- Reduce nesting by using guard clauses.
    - Invert conditions for early return
    - In loops, use early `continue` for invalid items instead of nesting
    - Max 2-3 nesting levels
    - Extract helpers for long methods

## Code Style

- `Must` prefix for panicking functions
- Enums: use `iota + 1` to start at one, distinguishing intentional values from zero default

## Error Handling

- Errors are values. Design APIs around that.
- Wrap with context using low-cardinality strings: `fmt.Errorf("get config: %w", err)` rather than `fmt.Errorf("get config from %s: %w", path, err)`. APM tools that group by message text can collapse the same root cause across instances; high-cardinality wraps fragment the dashboard.
- Variable data (IDs, names, paths) belongs in the structured log record at the **application boundary** (HTTP middleware, gRPC interceptor, top-level main), not at the wrap site. Intermediate layers wrap and return; the boundary observes the full chain and logs once with `slog.Error(..., "user_id", id, "error", err)`.
- Caveat: if no boundary in your service preserves the variable as a structured field, embedding it in the wrap is the lesser evil — losing the data is worse than losing APM grouping. Treat low-cardinality as an optimization, not a correctness rule. Audit for `zap.String("user_id", id)` / `slog.Error(... "user_id", id ...)` near the call boundary before stripping the variable from a wrap.
  ```go
  // Bad: high-cardinality error string -- APM sees each user as a unique error
  return fmt.Errorf("fetch user %s: %w", userID, err)

  // Good: stable wrap; the boundary handler logs user_id structurally
  return fmt.Errorf("fetch user: %w", err)
  ```
- Generic error matching: `errors.AsType[T]` (Go 1.26+) replaces the `var t *MyErr; errors.As(err, &t)` dance
- Join multiple errors: `errors.Join(err1, err2, err3)` (Go 1.20+)
- Avoid verb prefixes that accumulate through the stack: "failed to", "error", "could not", "unable to". Use bare context instead (`"connect: %w"` not `"failed to connect: %w"`, `"fetch config: %w"` not `"error fetching config: %w"`)
- Always check errors immediately before using returned values — in Go 1.21–1.24, the compiler could reorder statements and execute method calls on nil receivers before the error check ran, causing panics (fixed in Go 1.25)

### Error Strategy (Opaque Errors First)

Prefer **opaque error handling**: treat errors as opaque values, don't inspect internals. This minimizes coupling.

Three strategies in order of preference:

1. **Opaque errors** (preferred): return and wrap errors without exposing type or value. Callers only check `err != nil`.
2. **Sentinel errors** (`var ErrNotFound = errors.New(...)`): use sparingly for expected conditions callers must distinguish. They become public API.
3. **Error types** (`type NotFoundError struct{...}`): use when callers need structured context. Also public API — avoid when opaque or sentinel suffices.

### Assert Behavior, Not Type

When you must inspect errors beyond `errors.Is`/`errors.As`, assert on **behavior interfaces** instead of concrete types:

```go
type temporary interface {
    Temporary() bool
}

func IsTemporary(err error) bool {
    te, ok := err.(temporary)
    return ok && te.Temporary()
}
```

### Handle Errors Once

An error should be handled exactly once. Handling = logging, returning, or degrading gracefully. Never log and return — duplicates without useful context.

```go
// Bad: logs AND returns
if err != nil {
    log.Printf("connect failed: %v", err)
    return fmt.Errorf("connect: %w", err)
}

// Good: wrap (low-cardinality) and return; the boundary logs once
if err != nil {
    return fmt.Errorf("connect: %w", err)
}
```

Wrap with context at each layer; log/handle only at the application boundary. The boundary picks up variable data (`addr`, `user_id`, etc.) from `ctx`-scoped log fields or from a structured `slog.Error` call there — not from each wrap site.

### Context with Cause (Go 1.21+)

Propagate cancellation reasons through context:

```go
ctx, cancel := context.WithCancelCause(parent)
cancel(fmt.Errorf("shutdown: %w", reason))

// Later retrieve the cause
if cause := context.Cause(ctx); cause != nil {
    log.Printf("context cancelled: %v", cause)
}
```

## Generics

- Constraints: `comparable` for map keys, `cmp.Ordered` for sortable types
- Custom constraints with type sets: `type Number interface { ~int | ~int64 | ~float64 }`
- Generic type alias (Go 1.24+): `type Set[T comparable] = map[T]struct{}`
- Self-referential constraints (Go 1.26+): `type Adder[A Adder[A]] interface { Add(A) A }`
- Prefer concrete types when generics add no value

## Built-in Functions

- `min(a, b, c)` and `max(a, b, c)` - compute smallest/largest values (Go 1.21+)
- `clear(m)` - delete all map entries; `clear(s)` - zero all slice elements (Go 1.21+)
- `new(expr)` - allocate and initialize with value (Go 1.26+): `ptr := new(computeValue())`

## Testing

See [Go testing reference](reference/go-testing.md) — covers table-driven tests, benchmarks (`b.Loop()`, Go 1.24+), testify assertion conventions (prefer `require.Zero`/`Empty`/`Nil`, `ErrorIs`, `JSONEq`), test doubles via nil-embedded interfaces, practices (`TestFooBar` naming, `t.Helper`/`Cleanup`/`Context`/`Chdir`/`Parallel`, `-race`, `goleak` for leak detection), and `testing/synctest` (Go 1.25+) for deterministic concurrent tests.

## Concurrency

Share memory by communicating -- channels orchestrate; mutexes serialize.

### Choosing a Synchronization Primitive

| Need | Use | Why |
|------|-----|-----|
| Single counter/flag | `atomic` | Lock-free, simplest |
| Protect shared struct | `sync.Mutex` / `sync.RWMutex` | Direct, no goroutine overhead |
| Transfer ownership of data | Unbuffered channel | Synchronizes sender and receiver |
| Fan-out/fan-in, pipelines | Buffered channel + `select` | Composable, supports cancellation |
| N goroutines, first error aborts | `errgroup.WithContext` | Propagates cancellation |
| N goroutines, collect all errors | `errgroup` + `errors.Join` | No short-circuit |
| One-time init | `sync.Once` / `sync.OnceValue` | Race-free lazy init |
| Deduplicate concurrent calls | `singleflight.Group` | Coalesces in-flight requests |

- `sync.WaitGroup.Go()` (Go 1.25+): combines Add(1) + goroutine launch
  ```go
  var wg sync.WaitGroup
  wg.Go(func() { work() })  // Combines Add(1) + go
  wg.Wait()
  ```
- Make goroutine lifetime explicit; document when/why they exit
- Detect leaks with `GOEXPERIMENT=goroutineleakprofile` (Go 1.26+)
- Prefer synchronous functions; let callers add concurrency if needed
- `sync.Once` helpers: `sync.OnceFunc()`, `sync.OnceValue()`, `sync.OnceValues()` (Go 1.21+)
- Pointer receivers with mutexes (struct copy breaks lock semantics)
- Don't embed mutex (exposes Lock/Unlock to callers); use a named field instead
- Prevent copying of structs with mutexes or goroutine state using `noCopy`:
  ```go
  type Server struct {
      noCopy noCopy // go vet reports "copies lock value" if struct is copied
      mu     sync.Mutex
  }
  type noCopy struct{}
  func (*noCopy) Lock()   {}
  func (*noCopy) Unlock() {}
  ```
- Channels: sender closes, receiver checks; never close from receiver side or with multiple concurrent senders

### Channel Axioms

| Operation | nil channel | closed channel |
|-----------|-------------|----------------|
| Send      | blocks forever | **panics** |
| Receive   | blocks forever | returns zero value |
| Close     | **panics** | **panics** |

- Nil channels are useful in `select` to disable a case
- Use `for range ch` to receive until closed
- Check closure with `v, ok := <-ch`

## Iterators (Go 1.22+)

### Range Over Integers (Go 1.22+)
```go
for i := range 10 {
    fmt.Println(i)  // 0..9
}
```

### Range Over Functions (Go 1.23+)
```go
// String iterators
for line := range strings.Lines(s) { }
for part := range strings.SplitSeq(s, sep) { }
for field := range strings.FieldsSeq(s) { }

// Slice iterators
for i, v := range slices.All(items) { }
for v := range slices.Values(items) { }
for v := range slices.Backward(items) { }
for chunk := range slices.Chunk(items, 3) { }

// Map iterators
for k, v := range maps.All(m) { }
for k := range maps.Keys(m) { }
for v := range maps.Values(m) { }

// Collect iterator to slice
keys := slices.Collect(maps.Keys(m))
sorted := slices.Sorted(maps.Keys(m))

// Custom iterator
func (s *Set[T]) All() iter.Seq[T] {
    return func(yield func(T) bool) {
        for v := range s.items {
            if !yield(v) {
                return
            }
        }
    }
}
```

## Interface Design

- Accept interfaces, return concrete types
- Define interfaces at the consumer, not the provider; keep them small (1-2 methods)
- Compile-time interface check: `var _ http.Handler = (*MyHandler)(nil)`
- For single-method dependencies, use function types instead of interfaces
- Don't embed types in exported structs (exposes methods and breaks API compatibility)

## Slice & Map Patterns

- Pre-allocate when size known: `make([]User, 0, len(ids))`
- Nil vs empty: `var t []string` (nil, JSON null) vs `t := []string{}` (non-nil, JSON `[]`)
- Copy at boundaries with `slices.Clone(items)` to prevent external mutations
- Prefer `strings.Cut(s, "/")` over `strings.Split` for prefix/suffix extraction

## Common Patterns

### Options Pattern
Define `type Option func(*Config)`. Create `WithX` functions returning `Option` that set fields. Constructor takes `...Option`, applies each to default config.

### Default Values (Go 1.22+)
Use `cmp.Or(a, b, c)` to return first non-zero value—e.g., `cmp.Or(cfg.Port, envPort, 8080)`.

### Context Usage
- `context.WithoutCancel(ctx)` (Go 1.21+): derive a context that keeps values but ignores parent cancellation. Use for background work that must outlive the request (async cleanup, audit logging)

### HTTP Best Practices
- Use `http.Server{}` with explicit `ReadTimeout`/`WriteTimeout`; avoid `http.ListenAndServe`
- Method-based routing (Go 1.22+): `mux.HandleFunc("GET /items/{id}", h)`, extract via `r.PathValue("id")`. Greedy wildcard: `{path...}`

### AIP: Resource-Oriented gRPC APIs

For building resource-oriented gRPC APIs following [Google AIP](https://google.aip.dev/) standards with [einride/aip-go](https://github.com/einride/aip-go), see [AIP reference](reference/go-aip.md) -- covers resource names, standard methods (CRUD), pagination, filtering, ordering, field masks, and field behavior annotations.

### Security Helpers
- CSRF: `http.CrossOriginProtection(handler)` (Go 1.25+) -- rejects cross-origin state-changing requests
- Path traversal prevention: `os.OpenRoot("/var/data")` (Go 1.24+) -- chroot-like scoped file access
- GC cleanup: `runtime.AddCleanup(obj, fn)` (Go 1.24+) -- replaces `SetFinalizer`, supports multiple cleanups per object

## Structured Logging (log/slog)

- JSON handler for production: `slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))`
- `slog.ErrorContext(ctx, ...)` over `slog.Error(...)` so handler-bound trace/request IDs propagate

## Common Gotchas

### Nil Safety

| Operation | nil map | nil slice | nil channel |
|-----------|---------|-----------|-------------|
| Read/index | zero value | **panics** | blocks forever |
| Write | **panics** | **panics** (index) | blocks forever |
| `len` / `cap` | 0 | 0 | 0 |
| `range` | 0 iterations | 0 iterations | blocks forever |
| `append` | N/A | works (returns new) | N/A |
| `delete` | no-op | N/A | N/A |
| `close` | N/A | N/A | **panics** |

Don't add nil guards for values that a dependency (database, library, protocol) guarantees non-nil. Trust the contract; redundant checks add noise without safety. Only guard at true system boundaries (user input, external APIs, untrusted data).

### Slice Aliasing

`append` on a sub-slice can silently mutate the original if capacity remains:
```go
a := []int{1, 2, 3, 4}
b := a[:2]           // b shares a's backing array
b = append(b, 99)    // overwrites a[2]! a is now [1, 2, 99, 4]
```
Fix with full-slice expression to cap the capacity:
```go
b := a[:2:2]         // len=2, cap=2 -- append allocates a new array
b = append(b, 99)    // a is unchanged
```

### Copy Semantics

| Type | Assignment copies... |
|------|---------------------|
| bool, int, float, complex, string | value (safe) |
| array | all elements (deep) |
| struct | all fields (shallow -- pointer fields share referent) |
| slice | header only (shares backing array) |
| map | header only (shares buckets) |
| pointer, func, channel | pointer (shares referent) |
| interface | header only (shares underlying value if pointer) |

Use `slices.Clone` / `maps.Clone` for shallow copies at API boundaries.

- `nil` interface vs `nil` concrete: `var err error = (*MyError)(nil)` → `err != nil` is true (typed nil)
- Loop variables: each iteration has own copy (Go 1.22+); older versions share
- Timer/Ticker channels: capacity 0 (Go 1.23+); previously capacity 1

## Linting

Use `golangci-lint` with recommended linters: errcheck, govet, staticcheck, gocritic, gofumpt, wrapcheck, errorlint. See [linting reference](reference/linting.md) for the full `.golangci.yml` config template and commands. Projects with custom/private linters build a dedicated binary via the module plugin system (`.custom-gcl.yml` → `golangci-lint custom`) and must run that binary, not the one on PATH — see the same reference.

## Pre-Commit

Check for Makefile targets first (`make help`, or read Makefile). Common targets:
- `make lint` or `make check`
- `make test`
- `make build`

Fallback if no Makefile:
1. `go build ./...`
2. `go test -v -race ./...`
3. `golangci-lint run`
4. `go fix ./...` (Go 1.26+: modernizes code to latest idioms)
5. `gofmt -w .` or `goimports -w .`
6. `go mod tidy`

## Performance

### Profile-Guided Optimization (PGO)
```bash
# Collect profile
go test -cpuprofile=default.pgo

# PGO automatically enabled if default.pgo exists in main package
go build  # Uses default.pgo

# Typical 2-7% performance improvement
```

## Security

Based on OWASP Go Secure Coding Practices. See [security checklist and guides](reference/security-checklist.md) for the full checklist, detailed per-topic guides (input validation, auth, crypto, sessions, TLS, CSRF, file security, XSS, access control, logging), and security scanning tools (gosec, govulncheck, trivy).

---
> Source: [gonzaloserrano/gopilot](https://github.com/gonzaloserrano/gopilot) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-16 -->

