Use when a ticket adds or changes Go code and it must follow the repo's Go conventions — idiomatic Go, explicit error handling and wrapping, small interfaces, correct pointer-receiver rules, and table-driven tests run with the race detector. Invoke for "add this in Go", "fix the go vet/build issues", "add the handler/service", or as the language pack for any Go change.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add tmj-90/gaffer --skill go-conventions --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Go Conventions?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/tmj-90-go-conventions)More formats (shields.io, HTML) on the badges page.
---
name: go-conventions
description: Use when a ticket adds or changes Go code and it must follow the repo's Go conventions — idiomatic Go, explicit error handling and wrapping, small interfaces, correct pointer-receiver rules, and table-driven tests run with the race detector. Invoke for "add this in Go", "fix the go vet/build issues", "add the handler/service", or as the language pack for any Go change.
stack: [go]
area: language
---
# Write idiomatic Go
Add Go that reads as idiomatic, handles every error explicitly, and matches the repo's
existing patterns — simple and correct, the Go way.
## Steps
1. **Read the lore first.** Call `search_lore` (Memory MCP) for the repo's Go
conventions and respect `go.mod` (module path, Go version), the linter config
(`golangci-lint`), and any architecture ADRs. Keep packages cohesive and named for
what they provide.
2. **Find a sibling package** and copy its patterns — package layout, error handling,
how interfaces are defined and consumed, and how tests are organised.
3. **Handle every error explicitly.** Never discard an `error` with `_` unless it is
genuinely ignorable and commented why. **Wrap with context** using `fmt.Errorf("doing
X: %w", err)` so the chain is inspectable with `errors.Is`/`errors.As`. Return early
on error; avoid deep nesting.
4. **Keep interfaces small** and **define them at the consumer**, not the producer.
Accept interfaces, return concrete types. Don't add an interface speculatively.
5. **Pointer-receiver rules:** be consistent within a type. Use a pointer receiver when
the method mutates the receiver, the struct is large, or any method needs a pointer
receiver (so the method set stays consistent); use value receivers for small immutable
value types.
6. **Concurrency with care.** Pass `context.Context` as the first argument to anything
that blocks or spans a request; never store a `Context` in a struct. Guard shared state;
prefer channels/`sync` primitives over data races. Always have a defined goroutine exit.
7. **Idioms:** `defer` for cleanup, zero-value-useful structs, no naked returns in long
functions, no stuttering names (`http.HTTPServer` → `http.Server`). Run `gofmt`/`goimports`.
8. **Test table-driven.** Use sub-tests (`t.Run`) over a `[]struct` of cases; cover error
paths; run with `-race`. Use `t.Helper()` in assertion helpers.
9. **Verify + evidence.** Run `go vet`, `go build`, and `go test -race ./...`, record
`test_output` via the `record-evidence` skill, and submit for review.
## Build / Test
- **Build/vet:** `go build ./...` and `go vet ./...`.
- **Lint:** `golangci-lint run` (when the repo configures it).
- **Tests:** `go test -race ./...`; coverage via `go test -cover ./...`.
- The DoD is verified by the repo's configured test/coverage commands — run them
(with `-race`) and record the output; a clean vet + green race-tested run is the evidence.
## Review checklist (a Go reviewer must check)
- **Every error is checked** — no silently dropped `err`; ignores are explicit and commented.
- **Errors are wrapped with `%w`** and context, so `errors.Is`/`errors.As` work; no
`fmt.Errorf("%v", err)` that breaks the chain.
- **Interfaces are small and consumer-side**; functions accept interfaces, return concrete types.
- **Receiver types are consistent** across a type's method set; pointer vs value chosen deliberately.
- **`context.Context` is the first param** of blocking/request-scoped calls and never stored in a struct.
- **No data races** — shared state is guarded; goroutines have a defined exit; `-race` passes.
- **`gofmt`/`goimports` clean** and `go vet` reports nothing.
- **Tests are table-driven** with sub-tests and cover error cases, not only the happy path.
## Rules
- Match the repo's Go version, module layout, and linter config exactly.
- Check and wrap every error (`%w`); return early; no swallowed errors.
- Small consumer-side interfaces; consistent receivers; `context.Context` first, never stored.
- Table-driven tests run with `-race`; `gofmt`/`go vet` clean before review.
## Capture lore
This skill is one of the places durable, reusable knowledge naturally surfaces:
**A Go convention this repo enforces beyond the obvious — an error-wrapping pattern, a package-boundary rule, a concurrency invariant, or a linter constraint.** That kind of fact is *lore*. Capture it via the **lore-capture
protocol in your brief** (`CLAUDE.factory.md`, step 11 "Memory contribution"):
call the Memory MCP `suggest_lore` once at the close of your work — reusable
conventions, gotchas, decisions, and boundaries only, never per-ticket trivia.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!