Help us improve
Share bugs, ideas, or general feedback.
From gopilot
Writes, reviews, and debugs idiomatic Go code covering error handling, table-driven testing, concurrency patterns, OWASP security, and resource-oriented gRPC APIs per Google AIP with einride/aip-go.
npx claudepluginhub gonzaloserrano/gopilot --plugin gopilotHow this skill is triggered — by the user, by Claude, or both
Slash command
/gopilot:gopilotThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This SKILL.md is a SUMMARY. The files under `reference/` carry stricter rules
reference/access-control.mdreference/authentication.mdreference/cryptography.mdreference/csrf.mdreference/database-security.mdreference/error-handling.mdreference/file-security.mdreference/go-aip.mdreference/go-testing.mdreference/input-validation.mdreference/linting.mdreference/logging.mdreference/security-checklist.mdreference/session-management.mdreference/tls-https.mdreference/xss.mdGo language conventions, idioms, and toolchain. Invoke when task involves any interaction with Go code — writing, reviewing, refactoring, debugging, or understanding Go projects.
Provides idiomatic Go patterns for backend APIs with Gin, Echo, Fiber: standard project structure, custom error handling, handler dependency injection, concurrency best practices.
Guides Go code with pedantic best practices: error wrapping using fmt.Errorf/%w, interface design (accept interfaces return structs), package naming, struct field ordering, receiver naming, golangci-lint config.
Share bugs, ideas, or general feedback.
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.
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).