skills/gopilot/SKILL.md
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.
npx skillsauth add gonzaloserrano/gopilot gopilotInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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, helpersreference/go-aip.md — Google AIP and einride/aip-go for resource-oriented gRPCreference/error-handling.md — security-sensitive error patternsreference/logging.md — structured slog, secret redaction, correlation IDsreference/linting.md — golangci-lint configurationreference/security-checklist.md — OWASP Go pre-deploy checklistreference/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)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 sourcego doc ./internal/foo — local packagegopls definition <file>:<line>:<col> — jump to definitiongopls references <file>:<line>:<col> — find call sitesgopls symbols <file> — list package symbolsUse these over inferring APIs. Hallucinated signatures fail at compile time; go doc is faster than the build-fail loop.
bytes.Buffer and sync.Mutex work without init. When zero values compose, there's less API.continue for invalid items instead of nestingMust prefix for panicking functionsiota + 1 to start at one, distinguishing intentional values from zero defaultfmt.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.slog.Error(..., "user_id", id, "error", err).zap.String("user_id", id) / slog.Error(... "user_id", id ...) near the call boundary before stripping the variable from a wrap.
// 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)
errors.AsType[T] (Go 1.26+) replaces the var t *MyErr; errors.As(err, &t) danceerrors.Join(err1, err2, err3) (Go 1.20+)"connect: %w" not "failed to connect: %w", "fetch config: %w" not "error fetching config: %w")Prefer opaque error handling: treat errors as opaque values, don't inspect internals. This minimizes coupling.
Three strategies in order of preference:
err != nil.var ErrNotFound = errors.New(...)): use sparingly for expected conditions callers must distinguish. They become public API.type NotFoundError struct{...}): use when callers need structured context. Also public API — avoid when opaque or sentinel suffices.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()
}
An error should be handled exactly once. Handling = logging, returning, or degrading gracefully. Never log and return — duplicates without useful context.
// 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.
Propagate cancellation reasons through context:
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)
}
comparable for map keys, cmp.Ordered for sortable typestype Number interface { ~int | ~int64 | ~float64 }type Set[T comparable] = map[T]struct{}type Adder[A Adder[A]] interface { Add(A) A }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())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.
Share memory by communicating -- channels orchestrate; mutexes serialize.
| 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
var wg sync.WaitGroup
wg.Go(func() { work() }) // Combines Add(1) + go
wg.Wait()
GOEXPERIMENT=goroutineleakprofile (Go 1.26+)sync.Once helpers: sync.OnceFunc(), sync.OnceValue(), sync.OnceValues() (Go 1.21+)noCopy:
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() {}
| Operation | nil channel | closed channel | |-----------|-------------|----------------| | Send | blocks forever | panics | | Receive | blocks forever | returns zero value | | Close | panics | panics |
select to disable a casefor range ch to receive until closedv, ok := <-chfor i := range 10 {
fmt.Println(i) // 0..9
}
// 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
}
}
}
}
var _ http.Handler = (*MyHandler)(nil)make([]User, 0, len(ids))var t []string (nil, JSON null) vs t := []string{} (non-nil, JSON [])slices.Clone(items) to prevent external mutationsstrings.Cut(s, "/") over strings.Split for prefix/suffix extractionDefine type Option func(*Config). Create WithX functions returning Option that set fields. Constructor takes ...Option, applies each to default config.
Use cmp.Or(a, b, c) to return first non-zero value—e.g., cmp.Or(cfg.Port, envPort, 8080).
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.Server{} with explicit ReadTimeout/WriteTimeout; avoid http.ListenAndServemux.HandleFunc("GET /items/{id}", h), extract via r.PathValue("id"). Greedy wildcard: {path...}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.
http.CrossOriginProtection(handler) (Go 1.25+) -- rejects cross-origin state-changing requestsos.OpenRoot("/var/data") (Go 1.24+) -- chroot-like scoped file accessruntime.AddCleanup(obj, fn) (Go 1.24+) -- replaces SetFinalizer, supports multiple cleanups per objectslog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))slog.ErrorContext(ctx, ...) over slog.Error(...) so handler-bound trace/request IDs propagate| 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).
append on a sub-slice can silently mutate the original if capacity remains:
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:
b := a[:2:2] // len=2, cap=2 -- append allocates a new array
b = append(b, 99) // a is unchanged
| 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)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.
Check for Makefile targets first (make help, or read Makefile). Common targets:
make lint or make checkmake testmake buildFallback if no Makefile:
go build ./...go test -v -race ./...golangci-lint rungo fix ./... (Go 1.26+: modernizes code to latest idioms)gofmt -w . or goimports -w .go mod tidy# 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
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).
tools
TDD with baby steps for Go. Use when writing tests, doing TDD, practicing red-green-refactor, or when test cycles feel too large and risky. Also use when the user asks about incremental test development, test-first workflow, or wants help breaking a feature into small testable steps. Covers table-driven tests, testify, t.Run subtests, t.Helper, transformation priority premise, and incremental test progression.
tools
Detect and fix Go error handling antipatterns across a codebase. Use when auditing error handling, fixing double-handled errors, removing log-and-return patterns, cleaning up log-and-wrap helpers, or when the user asks to analyze error handling hygiene, find error handling violations, or ensure errors are handled exactly once. Covers detection patterns, classification of true vs false positives, fix strategies for interior vs boundary code, and verification steps.
development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.