Go Development
Apply this skill when writing, maintaining, or reviewing Go code.
Supporting Files
- tooling.md — Makefile targets, golangci-lint v2 config, CI toolchain conventions, GOTOOLCHAIN behavior
- patterns.md — Error handling, struct layout, and common patterns
Mode Detection
| Signal | Mode |
|---|---|
| "write", "create", "add", "scaffold", "implement", "new" | WRITE |
| "fix", "refactor", "update", "change", "migrate", "improve" | MAINTAIN |
| "review", "check", "audit", "look at", "what do you think" | REVIEW |
WRITE Mode
Step 1: Greenfield or brownfield?
Greenfield (new repo or new package):
- Use templates from tooling.md as starting point
- Standard layout:
cmd/for entry points,internal/for private packages,pkg/for public packages - Apply all rules below from the start
Brownfield (existing repository):
- Read existing
Makefile,golangci-lintconfig, andgo.modbefore writing anything - Match existing patterns — don't retrofit conventions the repo hasn't adopted
- Check
CLAUDE.md/AGENTS.mdfor repo-specific guidance first
Step 2: Apply when writing any code
Linter compliance — read the repository's golangci-lint configuration before changing code. Common rules that surprise people:
noinlineerr: splitif err := x(); err != nilinto two lines — see patterns.mdwsl_v5: add a blank line beforeifwhen multiple statements precede itrevive: mark unused parameters with_
Error handling:
- Never swallow errors — always check and propagate or wrap
- Wrap with context:
fmt.Errorf("doing X: %w", err) - See patterns.md for the noinlineerr split pattern
Module hygiene:
go.modgodirective = minimum Go version required to build — see tooling.md before bumping it
MAINTAIN Mode
- Read before writing — check existing patterns with Read/Grep
- Run checks after editing —
make lintandmake test; see tooling.md for standard targets - Match existing patterns — preserve style even if it differs from greenfield defaults
- Don't change tooling unless the user explicitly asks
REVIEW Mode
Go through the diff and report findings. See patterns.md for detailed pattern checks.
Blockers (Critical — must fix before merge)
- Swallowed errors:
errreturned but not checked, or_ = someFunc()on error-returning function -
|| return 0/|| truein shell scripts in the repo — silently converts failures to success; see review-pull-request skill -
go.modgodirective bumped above the CI toolchain version whenGOTOOLCHAIN=local— will fail all pipelines; see tooling.md - Secrets or credentials hardcoded
- Race conditions: shared state accessed without synchronization
Suggestions
- Missing error wrapping context (
fmt.Errorf("...: %w", err)preferred over bareerr) - Inline error assignment not split for
noinlineerrlint rule (will fail CI) - Missing blank line before
ifblock when multiple statements precede it (wsl_v5) - Unused parameter not named
_(revive)
NITs
- Abbreviated variable name where the full word is short and unambiguous (
bid→buildID,pkgs→packages,pfor a package struct →pkg) — see patterns.md - Exported identifier missing godoc comment
- Magic constant that should be a named
const - Test helper not calling
t.Helper() - Error string contains
\n— Go style requires single-line errors; see patterns.md - Package named
validate,util,helper,common— too generic; prefer a name that describes what the package actually does (e.g.layout,checksum,dralayout)
For version bump PRs: always check GOTOOLCHAIN behavior — see tooling.md.
When a blocker is found, grep for the same pattern across the codebase and include all instances in the review comment.
Brownfield Decision Tree
golangci-lint config present?
- Yes → run
make lintwith existing config; don't add or remove linters - No → add
.golangci.ymlonly if the user asks
Go version in go.mod?
- Check against CI toolchain before bumping — see tooling.md
Makefile present?
- Yes → check for
test,lint,fmt,buildtargets; use them - No → run
go test ./...andgolangci-lint rundirectly