spf13/go-skills/go

Claude Code and agent skill by spf13/go-skills

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Expert Go programming skill authored by spf13 (former Go team lead, author of Cobra, Viper, Hugo, Afero). Covers idiomatic Go — package design, error handling, interfaces, concurrency, testing, and project layout, current through Go 1.27. Use whenever Go code is written, reviewed, debugged, or refactored — any .go file, go.mod, CLI tool, or web service, and whenever the user mentions Go or golang, even if they don't ask for "idiomatic" code.

Install

npx vskill@latest install spf13/go-skills/go

SKILL.md

Idiomatic Go: The Go Way

Idiomatic Go patterns and best practices for building robust, efficient, and maintainable applications.

When to Activate

  • Writing new Go code
  • Reviewing or auditing existing Go code
  • Refactoring Go code (especially code that looks like Java/Spring Boot patterns in Go)
  • Designing Go packages, modules, or APIs
  • Choosing between stdlib and third-party libraries
  • Any question about Go project structure, error handling, concurrency, or testing

Version Gating: Check the Module's Floor First

Every version-gated feature is a floor: the module's packages must compile on the release that introduced it. Before recommending such a feature, establish the floor and recommend only what it supports.

  1. Read the go directive in the module's go.mod. That version is the floor for the module's own packages — a module declaring go 1.21 must still compile on Go 1.21, whatever toolchain you happen to be running.
  2. Check the file's build constraints. A file behind //go:build go1.24 may use Go 1.24 features even when the module's go directive is older.
  3. If the floor is unknown, read go.mod or ask. Never guess, and never assume the reader is on the latest release.

The inverse error is just as real: do not flag an idiom as unavailable in a module whose floor already permits it, and never raise a module's go directive merely to reach a convenience helper — a go directive is a floor imposed on every dependent of the module.

The Version → Feature Index

One line per feature, so availability is looked up here rather than guessed.

SinceFeature
1.17//go:build constraints, required in place of // +build lines
1.18Type parameters (generics)
1.18any as an alias for interface{}
1.19sync/atomic typed values (atomic.Int64, atomic.Pointer[T])
1.20errors.Join
1.21slices package
1.21maps package
1.21cmp package (cmp.Compare)
1.21min, max, clear builtins
1.21log/slog
1.21context.WithoutCancel
1.22math/rand/v2
1.22net/http ServeMux method and wildcard patterns
1.22for i := range n (range over integer)
1.22Per-iteration loop variables
1.22slices.Concat
1.22cmp.Or
1.23Range-over-func iterators (iter.Seq, iter.Seq2)
1.23maps.Keys / maps.Values as iterators
1.23slices.Collect / slices.Values / slices.Sorted
1.24encoding/json omitzero
1.24tool directive in go.mod
1.24Generic type aliases
1.24t.Context(), b.Loop(), t.Chdir()
1.25testing/synctest (synctest.Test)
1.25sync.WaitGroup.Go
1.26new(expr)
1.26Self-referential type-parameter constraints
1.26errors.AsType[T]
1.26log/slog.NewMultiHandler
1.26reflect iterators (Type.Fields, Type.Methods, Type.Ins, Type.Outs, Value.Fields, Value.Methods)
1.26t.ArtifactDir() / b.ArtifactDir() / f.ArtifactDir() with go test -artifacts
1.26B.Loop no longer inhibits inlining
1.26testing/cryptotest.SetGlobalRandom
1.26Green Tea GC on by default
1.26Heap base address randomization on 64-bit platforms
1.26runtime/pprof goroutineleak profile (experiment: GOEXPERIMENT=goroutineleakprofile)
1.26simd/archsimd (experiment: GOEXPERIMENT=simd)
1.26runtime/secret (experiment: GOEXPERIMENT=runtimesecret)
1.27Generic methods
1.27Struct literal keys may be any field selector
1.27Generalized function type inference
1.27stdversion vet check on by default in go test
1.27uuid package
1.27strings.CutLast / bytes.CutLast
1.27net/url URL.Clone / Values.Clone
1.27math/rand/v2 (*Rand).N
1.27testing/synctest.Sleep
1.27net/http/httptest.NewTestServer
1.27runtime/pprof goroutineleak profile generally available
1.27time timers and tickers always unbuffered; asynctimerchan removed
1.27Size-specialized small allocations (below 80 bytes)
1.27encoding/json backed by the encoding/json/v2 implementation
1.27simd (experiment: GOEXPERIMENT=simd)
1.27simd/archsimd on arm64 and WebAssembly (experiment: GOEXPERIMENT=simd)
Enforcement: stdversion

Since Go 1.27, go test runs the stdversion vet check by default. It reports the use of standard library symbols that are too new for the Go version in force in the referring file, as determined by the go directive in go.mod and the build tags on the file.

  • Run go vet ./... (or go test ./...) before calling version-gated code correct.
  • Treat a stdversion finding as a version-gating error, not a false positive: either the code is too new for the module's floor, or the floor is deliberately being raised and every dependent of the module must be told.
  • It covers standard library symbols only. Language features (new(expr), generic methods) and behavior changes that introduce no new symbol are still yours to gate by hand.

Core Principles

1. Clear is Better than Clever

Go favors readability and simplicity over abstraction and cleverness. Code should be obvious. If you have to read a function three times to understand its control flow, it needs to be rewritten.

go
// Idiomatic: Direct, linear control flow. Note: no "Get" prefix — Go omits it.
func LookupUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("finding user %s: %w", id, err)
}
return user, nil
}
2. Make the Zero Value Useful

Design types so their zero value is immediately usable without initialization. This eliminates boilerplate constructors. sync.Mutex and bytes.Buffer are the gold standard for this.

go
// Idiomatic: Ready to use immediately
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) Inc() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
3. Return Early, Keep the Happy Path Left

Handle errors and edge cases immediately and return. Do not use else blocks for the main logic. The "happy path" of your function should never be indented.

Write Less: The Necessity Ladder

Before writing code, stop at the first rung that holds. Climb after you understand the problem — read the code the change touches and trace the real flow first. If two rungs both work, take the higher one and move on.

  1. Does this need to exist? "We'll want it later" is not a need. Skip it, and say so in one line.
  2. Does the codebase already have it? Search before you write — the helper, type, or pattern may already live a package over. Re-implementing what is already here is the most common form of this mistake.
  3. Does the standard library do it? In Go this rung catches more than any other: net/http's ServeMux over a router, slog over a logging module, slices/maps/cmp over hand-rolled loops, errors.Join over multierr, math/rand/v2 over an RNG helper. See Standard Library: Use the New Packages.
  4. Does the toolchain or the platform already do it? These are free and already installed: //go:embed over an asset pipeline or a loader, os.Root over hand-rolled path confinement, sync.OnceValue over a lazy-init helper, the tool directive over a script, build tags over a shell wrapper, a CHECK constraint or a unique index over application code that re-implements it. In a service's templates, a native input beats a component library and its bundle.
  5. Is it already in `go.mod`? Use the dependency you already pay for. Never add a module for what a few lines or an existing import can do — but this is not a stdlib-only rule: golang.org/x/sync/errgroup, afero, and go-cmp are installed dependencies this skill recommends (see Concurrency Patterns and Testing Patterns), not things to hand-roll.
  6. Can it be one line? Write the one line.
  7. Only then: the minimum that works — no extra files, layers, or knobs. An interface with one implementation, a factory for one product, and a config field for a value that never changes are all rung-7 failures.

Rungs 3 and 4 name version-gated features (sync.OnceValue arrives in Go 1.21; os.Root and the tool directive in 1.24), so establish the module's floor before recommending one — see Version Gating.

Never cut: validation at a trust boundary, error handling that prevents data loss, security, cleanup, and anything explicitly requested. In Go that means the error is still checked and wrapped, the defer that releases the resource stays, the mutex that makes the map safe stays, and a non-trivial branch or parser still leaves one runnable test behind (see Testing Patterns). Laziness shortens the solution, never the reading, and never the checks.

Fix the root cause, not the reported path. A bug report names a symptom. Grep every caller of the function before touching it: one guard where all callers route through is a smaller diff than a guard at each call site, and it fixes the callers the ticket never mentioned.

Name the ceiling you accepted. When the minimal version has a known limit, record it where the limit lives: // yagni: whole-table lock — per-key locks if write throughput matters. A stated ceiling is a simplification; an unstated one is a landmine.

Package Organization: Flat by Default

Anti-Pattern: Using deeply nested directory trees or relying heavily on an internal/ folder by default to artificially enforce "Clean Architecture" layers. This leads to circular dependencies and difficult navigation.

1. The Single-Package Default

Start flat. If you are building a microservice or a simple tool, put everything in the root directory (or alongside your main.go). Only create a new package when you truly need a new namespace to clarify the code, or when you need to decouple a strictly independent domain.

One binary means main.go at the module root. Reach for cmd/<name>/ only when the module ships more than one binary — one directory per binary, nothing else in there.

2. The Proper Use of internal/

The internal/ directory has a specific compiler enforcement: it prevents other modules from importing the code inside it.

  • For Applications: If you are building an executable binary, nobody can import your code anyway. Using internal/ here is usually just adding unnecessary path depth.
  • For Libraries: Use internal/ sparingly. It should be reserved for complex subsystems where you need to share exported types between your own packages, but absolutely must prevent end-users from relying on those types.
// Idiomatic: A flat, feature-focused library or simple app
myproject/
├── main.go # Entry point (if application)
├── server.go # Core logic
├── config.go # Configuration
├── parser.go # Domain specific parsing
├── parser_test.go
├── go.mod
└── go.sum
3. Domain Packages for Service Applications

When an application has genuinely distinct, independently-testable domains, give each its own top-level package. The rule is still one level deep — no internal/ nesting, no Clean Architecture layers. Each package owns one concern. main.go wires them together.

The signal to create a package: can this domain be described in one sentence, and can it do its job without knowing the packages that call it? If yes, it earns its own package.

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Refreshed 8 Oct 2026, 22 days ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/spf13/go-skills/go/badge)](https://verified-skill.com/skills/spf13/go-skills/go)

Something wrong with this skill or its page? Tell us.