go-clean-architecture
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
Use when hardening Go code at API boundaries: copy slices/maps on entry and return, defer cleanup, verify interface compliance at compile time, model time with time.Time/time.Duration, design enum zero values, prefer crypto/rand, and inject clocks for testability. Apply
Claude Code
$ npx -y skills add muratmirgun/gophers --skill go-defensive --agent claude-code
Codex
$ npx skills add muratmirgun/gophers --skill go-defensive -a codex -y
Cursor
$ npx skills add muratmirgun/gophers --skill go-defensive -a cursor -y
Gemini CLI
$ npx skills add muratmirgun/gophers --skill go-defensive -a gemini-cli -y
GitHub Copilot
$ npx skills add muratmirgun/gophers --skill go-defensive -a github-copilot -y
OpenCode
$ npx skills add muratmirgun/gophers --skill go-defensive -a opencode -y
Windsurf
$ npx skills add muratmirgun/gophers --skill go-defensive -a windsurf -y
name: go-defensive description: "Use when hardening Go code at API boundaries: copy slices/maps on entry and return, defer cleanup, verify interface compliance at compile time, model time with time.Time/time.Duration, design enum zero values, prefer crypto/rand, and inject clocks for testability. Apply proactively when reviewing for robustness. Error-handling strategy: see go-error-handling." license: MIT compatibility: "Designed for Claude Code or similar AI coding agents. `crypto/rand.Text` examples assume Go 1.24+." allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)
Hardening Go code is not paranoia — it is the discipline of making your boundaries honest. Copy what crosses them, clean up what you opened, model time and randomness honestly, and never let a panic escape a package.
1. **Copy slices and maps at API boundaries.** They are reference types — leaking the backing array leaks mutation. 2. **`defer` the cleanup right after the acquire.** `f, err := os.Open(...); defer f.Close()`. 3. **Verify interface compliance at compile time:** `var _ I = (*T)(nil)`. 4. **Model time and durations with `time.Time` and `time.Duration`,** never raw ints. 5. **Inject `now func() time.Time`** instead of calling `time.Now()` directly in production code. 6. **Enums start at `iota + 1`** so the zero value is invalid. 7. **`crypto/rand` for secrets, never `math/rand`.** 8. **Panics never cross package boundaries.** Convert to errors at the edge. 9. **Avoid mutable package-level state.** Inject dependencies instead.
When you touch an exported function or method, walk this list in order:
| # | Check | |---|---| | 1 | Return errors, don't panic across boundaries | | 2 | Copy slices/maps you'll retain | | 3 | Copy slices/maps you'll return if internal state aliases them | | 4 | `defer` Close / Unlock / cancel right after the acquire | | 5 | Compile-time interface satisfaction check | | 6 | `time.Time` / `time.Duration` types, injected clock | | 7 | Enum zero = invalid (`iota + 1`) | | 8 | `crypto/rand` for any secret material |
// Receiving: copy a slice we'll retain
func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}
// Returning: copy a map so callers can't mutate our state
func (s *Stats) Snapshot() map[string]int {
out := make(map[string]int, len(s.counters))
for k, v := range s.counters {
out[k] = v
}
return out
}> Read [references/boundary-copying.md](references/boundary-copying.md) when deciding which boundaries actually need copies (and when copying is wasted work).
`defer` evaluates arguments at the `defer` statement and runs the call when the surrounding function returns (LIFO order):
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()Place `defer` immediately after the acquire — the proximity makes pair-correctness reviewable at a glance.
For locks:
mu.Lock() defer mu.Unlock()
Beware of `defer` inside loops — accumulated defers run only when the function returns, not when the iteration ends.
var _ http.Handler = (*Handler)(nil)
If `(*Handler)` ever stops satisfying `http.Handler`, the build fails. The line costs nothing at runtime and gives you a free contract.
// Bad — what unit is timeout?
type Config struct {
Timeout int
}
// Good
type Config struct {
Timeout time.Duration
}For wall-clock work, inject the clock so tests can pin time:
type Signer struct {
now func() time.Time
}
func NewSigner() *Signer {
return &Signer{now: time.Now}
}
// In tests:
s := &Signer{now: func() time.Time { return fixedTime }}> Read [references/time-and-enums.md](references/time-and-enums.md) for monotonic time, time zones, struct tags, and embedding tradeoffs.
import "crypto/rand"
// Go 1.24+
func APIKey() string { return rand.Text() }`math/rand` and `math/rand/v2` are predictable from a seed — never use them for keys, tokens, nonces, or any secret material.
`Must*` helpers panic on error. They are appropriate **only** at program initialization, where failure means the program cannot start:
var (
validID = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`)
tmpl = template.Must(template.ParseFiles("index.html"))
)Don't write `MustFoo` for runtime call sites — it shifts an error condition into a crash.
> Read [references/must-and-panic.md](references/must-and-panic.md) for writing custom `Must*`, recovering at goroutine boundaries, and distinguishing `panic` from `log.Fatal`.
// Bad — testing requires save/restore dance
var DB *sql.DB
// Good — pass the dependency
type Service struct {
db *sql.DB
}Constants and once-initialized lookup tables are fine. Mutable package-level vars are a code smell.
| Anti-pattern | Why it hurts | Do this instead | |---|---|---| | Storing the caller's slice without copying | Mutation aliasing | `make` + `copy` | | Returning the internal map directly | External mutation of state | Return a snapshot | | `time.Now()` in business logic | Hostile to tests | Inject `now func() time.Time` | | `var Timeout = 5` read as seconds elsewhere | Ambiguous unit | `time.Duration` | | `math/rand` for keys | Predictable from seed | `crypto/rand` | | `panic` to signal a domain error | Crashes the caller | Return an error | | `defer` inside a tight loop | Defers stack until function return | Wrap loop body in a function |
26 production-grade Go skills for Claude Code, Gemini CLI, and opencode. Battle-tested patterns from the Go community — codified as triggerable AI skills.
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
Invoke this skill to systematically review a Go change against community style standards before merging. Walks the diff topic by…
Use when writing or reviewing Go code for clarity, formatting, control flow, variable declarations, switch usage, and function…
Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker…
Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation,…
Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization,…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.