| name | gopilot |
| description | Go programming language skill for writing idiomatic Go code, code review, error handling, testing, concurrency, security, and program design. Use when writing, reviewing, debugging, or asking about Go code — even if the user doesn't explicitly mention 'Go best practices'. Also use when: reviewing Go PRs, debugging Go tests, fixing Go errors, designing Go APIs, implementing security-sensitive code, handling user input, authentication, sessions, cryptography, building resource-oriented gRPC APIs with Google AIP standards, configuring golangci-lint, setting up structured logging with slog, or any question about Go idioms and patterns. Covers table-driven tests, error wrapping, goroutine patterns, interface design, generics, iterators, stdlib patterns up to Go 1.26, OWASP security practices, and Google AIP (API Improvement Proposals) with einride/aip-go for pagination, filtering, ordering, field masks, and resource names. |
| license | MIT |
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
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:
- Opaque errors (preferred): return and wrap errors without exposing type or value. Callers only check
err != nil.
- Sentinel errors (
var ErrNotFound = errors.New(...)): use sparingly for expected conditions callers must distinguish. They become public API.
- 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:
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.
if err != nil {
log.Printf("connect failed: %v", err)
return fmt.Errorf("connect: %w", err)
}
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:
ctx, cancel := context.WithCancelCause(parent)
cancel(fmt.Errorf("shutdown: %w", reason))
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 — 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 |
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+)
for i := range 10 {
fmt.Println(i)
}
Range Over Functions (Go 1.23+)
for line := range strings.Lines(s) { }
for part := range strings.SplitSeq(s, sep) { }
for field := range strings.FieldsSeq(s) { }
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) { }
for k, v := range maps.All(m) { }
for k := range maps.Keys(m) { }
for v := range maps.Values(m) { }
keys := slices.Collect(maps.Keys(m))
sorted := slices.Sorted(maps.Keys(m))
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 standards with einride/aip-go, see AIP reference -- 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:
a := []int{1, 2, 3, 4}
b := a[:2]
b = append(b, 99)
Fix with full-slice expression to cap the capacity:
b := a[:2:2]
b = append(b, 99)
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 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:
go build ./...
go test -v -race ./...
golangci-lint run
go fix ./... (Go 1.26+: modernizes code to latest idioms)
gofmt -w . or goimports -w .
go mod tidy
Performance
Profile-Guided Optimization (PGO)
go test -cpuprofile=default.pgo
go build
Security
Based on OWASP Go Secure Coding Practices. See security checklist and guides 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).