Apply Go coding rules, design principles, and project conventions for maintainable Go code and repositories. Use when writing, reviewing, refactoring, or releasing Go code, or when working on Go-specific project structure, APIs, tests, CI, or binaries. Do not use for non-Go codebases or for language-agnostic tasks that do not depend on Go conventions.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Apply Go coding rules, design principles, and project conventions for maintainable Go code and repositories. Use when writing, reviewing, refactoring, or releasing Go code, or when working on Go-specific project structure, APIs, tests, CI, or binaries. Do not use for non-Go codebases or for language-agnostic tasks that do not depend on Go conventions.
This skill defines the non-negotiable idioms and design principles for writing Go code. Every piece of Go code produced must conform to these rules.
References
references/setup.md — Complete guide for initializing new Go projects, justfile templates, Cobra CLI setup, and XDG directory integration.
references/github-ci-cd-releases.md — GitHub Actions CI, tagged releases, GoReleaser config, or binary version metadata.
0. Toolchain, Build & Automation Rules
Project Setup & Environment Reference: For new Go project setup, justfile task automation templates, Cobra initialization, XDG directory helpers, and .gitignore baseline, see references/setup.md.
Latest Go Version: Always use the latest version of Go (currently at least Go 1.26+). Declare go 1.26 or higher in go.mod.
Binary Output Location (bin/): Compiled binaries must always be placed into the project-local bin/ directory using the project's actual application name (e.g., go build -o bin/<app-name> ./cmd/<app-name>). Never output binaries into root or source directories, and never output literally as bin/app (replace <app-name> with the project binary name). Always git-ignore bin/.
Task Automation (just & justfile): Use just for task automation, builds, tests, and project recipes via a justfile (e.g., just build, just test, just lint). Avoid raw uncoordinated shell scripts or legacy makefiles.
CLI Framework & Argument Parsing (Cobra & cobra-help-tree): For CLI applications, always use Cobra (github.com/spf13/cobra) for CLI command structure, flags, and argument parsing. Do not use the standard library flag package or write custom argument parsers. For help screen output, always use github.com/alexgorbatchev/cobra-help-tree (cobrahelptree.Setup(rootCmd)) to display aligned hierarchical tree-view help screens across all command levels.
Version Flag Output (--version): --version must return ONLY the raw version string (e.g., 1.2.3 or v1.2.3), followed by a newline. Do not include application names, banners, labels, or extra formatting (e.g., NOT app version 1.2.3 or Version: 1.2.3). Clean version output is strictly required for automated scripting, tooling, and CI/CD validation.
XDG Base Directory Specification: Applications must strictly follow XDG Base Directory conventions ($XDG_CONFIG_HOME, $XDG_DATA_HOME, $XDG_CACHE_HOME, $XDG_STATE_HOME) for user configuration, data, cache, and state directories unless otherwise explicitly specified by requirements or CLI flags. Leverage standard APIs like os.UserCacheDir() or os.UserConfigDir() with appropriate fallbacks.
1. Naming
Go naming communicates scope and intent through brevity. Names earn their length.
1.1 Variable Length Tracks Usage Distance
The farther a variable is from its declaration, the more descriptive its name should be. The closer it is, the shorter.
// GOOD — single-letter in tight scopefor i, v := range items {
process(v)
}
// GOOD — short name, used within a few lines
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// GOOD — descriptive name, lives across many lines or is a struct fieldtype Worker struct {
MaxRetryAttempts int
ShutdownTimeout time.Duration
}
// BAD — unnecessarily verbose in tight scopefor index, value := range items {
process(value)
}
// BAD — cryptic name that persists across a wide scope
t := time.Hour * 24 * 7// ... 40 lines later ...if elapsed > t { // what is t?
1.2 Receiver Names
Single-letter or two-letter abbreviation of the type. Never self or this. Consistent across all methods on the type.
Name interfaces by what they do, not what implements them. Single-method interfaces use the method name plus -er.
type Reader interface { Read(p []byte) (n int, err error) }
type Validator interface { Validate() error }
// NOT: type IReader interface { ... }// NOT: type ReaderInterface interface { ... }
1.4 Package Names
Short, lowercase, single-word. The package name is part of the call site — don't stutter.
// GOODpackage http // http.Clientpackage user // user.Create(...)// BADpackage httputil // httputil.HTTPClient — stutterpackage users // no pluralspackage userService // no camelCase
1.5 Exported vs Unexported
Export only what other packages need. Start unexported; promote to exported when a real consumer requires it. An unexported API surface is easier to change.
1.6 Acronyms
Acronyms are all-caps or all-lower depending on export status. Never mixed.
Interfaces in Go are powerful because they're implicit. That power is squandered when they exist only to satisfy a test double. An interface must have a concrete, production reason to exist.
3.1 When to Define an Interface
Define an interface when:
Two or more real types already satisfy it in production code.
The consumer genuinely does not care which implementation it gets (e.g., io.Reader — files, buffers, network connections all qualify).
A package boundary requires decoupling — the consuming package should not import the providing package.
3.2 When NOT to Define an Interface
Do not define an interface when:
Only one production implementation exists and the interface is being created solely so tests can swap in a mock. Test the real thing. Use a real database, a real HTTP server (httptest.NewServer), a real filesystem (t.TempDir()).
The interface mirrors the concrete type 1:1. If the interface has the same methods as the only struct that implements it, delete the interface and use the struct.
You're planning for a future that may never come. YAGNI. Add the interface when the second implementation actually appears.
3.3 Accept Interfaces, Return Structs
Functions should accept the narrowest interface they need and return concrete types. This maximizes flexibility for callers without hiding what's actually being returned.
// GOOD — accepts narrow interface, returns concretefuncProcessData(r io.Reader) (*Result, error) { ... }
// BAD — returns interface hiding the concrete typefuncNewService() ServiceInterface { ... }
3.4 Define Interfaces at the Consumer, Not the Provider
The package that uses the behavior defines the interface. The package that provides the behavior just exports a struct with methods.
// package orders — this is the consumertype PaymentCharger interface {
Charge(ctx context.Context, amount int) error
}
func(s *Service) Checkout(ctx context.Context, c PaymentCharger) error { ... }
// package stripe — this is the provider, no interface heretype Client struct { ... }
func(c *Client) Charge(ctx context.Context, amount int) error { ... }
3.5 Testing Without Interface Bloat
Preferred alternatives to mock-driven interfaces:
Technique
When to use
httptest.NewServer
Testing HTTP clients
t.TempDir()
Testing file operations
In-memory SQLite / testcontainers
Testing database interactions
Real struct with test config
Anything with a configurable dependency
Fakes (small, real implementations)
When a lightweight alternative exists
4. Error Handling
4.1 Always Handle Errors
Never discard errors with _. If you truly do not care, document why with a comment.
Use fmt.Errorf with %w to add context while preserving the error chain. The message should read as a call stack: what was being attempted.
if err != nil {
return fmt.Errorf("fetching user %d: %w", id, err)
}
4.3 Sentinel Errors and Custom Types
Define sentinel errors (var ErrNotFound = errors.New(...)) when callers need to branch on the error kind. Use custom error types when callers need structured data from the error. Otherwise, a wrapped string is fine.
4.4 Don't Panic
panic is for truly unrecoverable programmer errors (e.g., invalid regexp in init). Never panic on bad user input, network failure, or any runtime condition that can be handled.
If you can't name the package by what it does, the code probably belongs somewhere else. Move functions to the package that uses them, or name the package for the domain concept it owns.
5.3 Internal Packages
Use internal/ to prevent external import of implementation details. Anything not meant for outside consumption goes in internal/.
5.4 Minimal Package API Surface
A package should export the minimum needed. Start every type, function, and constant as unexported. Promote to exported only when an external package has a demonstrated need.
5.5 Never Commit Compiled Binaries
Compiled Go binaries (executables, .exe files, shared libraries, etc.) are platform-specific, huge, and must never be committed to git repositories. Always output binaries strictly into bin/, exclude bin/ from git using .gitignore, and distribute compiled assets solely through CI/CD pipelines, package registries, or release platforms.
6. Concurrency
6.1 Start Goroutines With Clear Ownership
Every goroutine must have a clear owner responsible for its lifecycle. The owner must ensure the goroutine exits cleanly.
Pass ctx as the first parameter. Respect cancellation. Don't store contexts in structs.
6.3 Protect Shared State
If data is shared across goroutines, protect it. Prefer channels for communication and sync.Mutex for direct state protection. Don't mix both for the same data.
6.4 Never Leak Goroutines
Every goroutine launched must have a shutdown path. Use context.Context, a done channel, or sync.WaitGroup to track and join goroutines on shutdown.
6.5 Be Deliberate With GOMAXPROCS
Go 1.25+ adjusts default GOMAXPROCS more intelligently (including container CPU limits on Linux and periodic updates when limits change). Do not hardcode GOMAXPROCS unless profiling shows a clear benefit for your workload.
7. Code Clarity and Intent
7.1 Write Obvious Code
If a reader has to pause and think about what a block of code does, it needs to be clearer. Techniques:
Extract a well-named function rather than adding a comment to explain a block.
Avoid clever one-liners. Two clear lines beat one clever line.
Use early returns to eliminate nesting and make the happy path obvious.
// GOOD — early return, flat structurefunc(s *Service) Process(ctx context.Context, id int64) error {
u, err := s.store.Get(ctx, id)
if err != nil {
return fmt.Errorf("getting user: %w", err)
}
if !u.Active {
return ErrInactive
}
return s.notify(ctx, u)
}
// BAD — nested, harder to followfunc(s *Service) Process(ctx context.Context, id int64) error {
u, err := s.store.Get(ctx, id)
if err == nil {
if u.Active {
return s.notify(ctx, u)
} else {
return ErrInactive
}
} else {
return fmt.Errorf("getting user: %w", err)
}
}
7.2 Comments Explain Why, Not What
The code shows what is happening. Comments explain why — business rules, non-obvious constraints, workarounds.
// GOOD// Retry on 503 because the upstream gateway occasionally returns// transient errors during deployment rollouts.if resp.StatusCode == http.StatusServiceUnavailable {
// BAD// Check if status code is 503if resp.StatusCode == http.StatusServiceUnavailable {
7.3 Function Size
If a function exceeds ~40 lines, look for extraction opportunities. This isn't a hard rule — some functions (table-driven tests, switch statements) are naturally longer. The test is readability, not line count.
7.4 Parameter Count
More than 3-4 parameters usually means you want an options struct or a rethink of the function's responsibility.
Design structs so their zero value is valid and useful. This reduces constructor boilerplate and makes the API easier to use.
// GOOD — zero value worksvar buf bytes.Buffer
buf.WriteString("hello")
// The same principle applied to your own typestype Limiter struct {
rate int// 0 means unlimited
burst int// 0 means default burst
}
7.6 Prefer Newer Readable Idioms When They Clarify Intent
Use post-1.22 language features when they make code clearer, not because they are new:
for range over iterator functions (Go 1.23+) can remove custom iterator boilerplate.
new(expr) (Go 1.26+) is a concise way to produce pointers to computed values in literals.
Keep the same readability bar: use the form that makes intent easiest to review for the team.
8. Standard Library First
Reach for the standard library before any third-party dependency. Go's stdlib is unusually rich. Common traps:
Don't pull in...
When stdlib has...
gorilla/mux
net/http.ServeMux (1.22+ has patterns)
logrus/zap (maybe)
log/slog (1.21+)
testify
testing + table-driven tests
uuid libraries
crypto/rand + explicit formatting
config libraries
os.Getenv + a small struct
Exception for CLI applications: Use Cobra (github.com/spf13/cobra) with github.com/alexgorbatchev/cobra-help-tree for CLI commands, argument handling, and tree-structured help screens instead of stdlib flag.
8.1 Manage Build/Dev Tools as Module Tools
For CLI tooling used by the repo (linters, generators, etc.), prefer tool directives in go.mod (Go 1.24+) over tools.go blank-import stubs.
This keeps tool dependencies explicit and lets you use native workflows like go get -tool, go install tool, and go tool.
Add a dependency only when it provides clear, substantial value that the stdlib cannot match with reasonable effort.
9. Quick Reference Checklist
Before producing any Go code, verify:
Go version is latest (at least Go 1.26+) declared in go.mod
Binaries are built strictly into the bin/ folder
Task automation uses just with a justfile (just build, just test, etc.)
CLI tools use Cobra (github.com/spf13/cobra) with github.com/alexgorbatchev/cobra-help-tree for flags, subcommands, argument parsing, and tree help screens
--version returns ONLY the version string (no app name, prefix, or extra text)
Follows XDG Base Directory conventions ($XDG_CONFIG_HOME, $XDG_CACHE_HOME, etc.) for user paths unless explicitly specified otherwise
Variable names match scope distance — short for tight, descriptive for wide
No duplicated logic blocks — extract on second occurrence
Every interface has 2+ production implementations or genuine decoupling need
Errors are wrapped with context using %w
No panics on runtime conditions
Goroutines have clear ownership and shutdown paths
Packages named for responsibility, not layer
Exported API is minimal — only what external consumers need
Standard library used unless a dependency provides substantial value
No compiled Go binaries committed to git repositories (enforced via .gitignore on bin/)
Module tool dependencies use tool directives (not tools.go blank imports)
10. Post-Cutoff Go Release Updates (Go 1.23+)
Only include these when relevant. This section intentionally tracks features released after the model training cutoff.
Go 1.23 (Aug 2024)
Language
for range now supports iterator functions (func(func() bool), func(func(K) bool), func(func(K, V) bool)) as range expressions.
Generic type aliases were introduced as a preview behind GOEXPERIMENT=aliastypeparams.
Tooling
Added opt-in Go telemetry (go telemetry on|off|local).
Added go env -changed to print only non-default effective environment settings.
Added go mod tidy -diff for non-mutating module tidy checks in CI.
Added godebug directive support in go.mod / go.work.
go vet gained the stdversion analyzer for version-incompatible symbol usage.
cmd/cgo added -ldflags support to avoid large CGO_LDFLAGS argument overflow issues.
trace became more resilient to partially broken trace data.
Go 1.24 (Feb 2025)
Language
Generic type aliases became fully supported.
Tooling
Added first-class module tool dependencies via tool directives in go.mod.
Added go get -tool and the tool meta-pattern (go get tool, go install tool).
go run and go tool executable outputs are now cached in the build cache.
Added structured JSON build output via go build -json / go install -json; expanded go test -json build event reporting.
Added GOAUTH for private module fetch authentication.
go build now embeds main module VCS version info (including +dirty when applicable).
Added GODEBUG=toolchaintrace=1 for toolchain selection debugging.
Cgo added #cgo noescape and #cgo nocallback performance annotations.
go vet added tests analyzer and improved checks in printf, buildtag, and copylock.
GOCACHEPROG cache protocol support graduated from experiment.
Go 1.25 (Aug 2025)
Language
No language changes affecting Go programs (spec cleanup removed “core types” terminology).
Tooling
go build -asan now enables leak detection by default at process exit.
Go distributions ship fewer prebuilt auxiliary tools; non-core tools are built on demand by go tool.
Added ignore directive in go.mod for directories excluded from package pattern matching.
Added go doc -http to launch docs in a local web server/browser.
Added go version -m -json for machine-readable embedded build info.
Added work package pattern to target all workspace/main-module packages.
go no longer auto-adds a toolchain line when updating go lines in go.mod / go.work.
go vet added waitgroup and hostport analyzers.
Go 1.26 (Feb 2026)
Language
Built-in new now accepts expressions, allowing inline initialization (for example new(yearsSince(born))).
Generic types may now self-reference in type parameter constraints (for example type Adder[A Adder[A]] interface { ... }).
Tooling
go fix was rewritten as the modernizer hub (analyzer-based fixers + //go:fix inline support).
go mod init now defaults new modules to an older, broadly compatible go version line.
cmd/doc and go tool doc were removed; go doc is the replacement.
pprof -http now defaults to flame graph view.
Comments explain why, code explains what
Zero values are useful
Tests are table-driven where applicable
go mod tidy -diff is clean for module hygiene checks
go vet is clean (including modern analyzers such as stdversion, tests, waitgroup, and hostport)
go fix has been considered when upgrading/migrating older idioms