go-clean-architecture
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase, Repository, Delivery layers, inward…
Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker pools, or fan-out/fan-in pipelines. Apply proactively whenever a goroutine is spawned, a shared field is mutated, or a channel is created, even if
$ npx -y skills add muratmirgun/gophers --skill go-concurrency --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/go-concurrencyContext preview
The summary Claude sees to decide when to auto-load this skill.
Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker pools, or fan-out/fan-in pipelines. Apply proactively whenever a goroutine is spawned, a shared field is mutated, or a channel is created, even if
name: go-concurrency description: "Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker pools, or fan-out/fan-in pipelines. Apply proactively whenever a goroutine is spawned, a shared field is mutated, or a channel is created, even if the user has not asked about concurrency. Does not cover context.Context patterns (see go-context)." license: MIT compatibility: "Designed for Claude Code or similar AI coding agents. Targets Go 1.21+ for typed atomics and slog. Notes Go 1.25 wg.Go, Go 1.26 testing/synctest, and Go 1.26 experimental goroutineleak profile where relevant." allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)
Goroutines are cheap, but every one you spawn is a resource you must own. The goal is **structured concurrency**: each goroutine has a clear owner, a predictable exit, and a way for the caller to wait and collect errors.
1. **Never start a goroutine without knowing how it will stop.** A blocked goroutine is not garbage-collected — it leaks. 2. **The caller must be able to wait.** Use `sync.WaitGroup`, `errgroup.Group`, or an explicit done channel. 3. **No goroutines in `init()`.** Expose `Start`/`Stop`/`Shutdown` so callers control the lifecycle. 4. **Share by communicating.** Default to channels; reach for `sync.Mutex` only when the problem is genuinely "protect a shared field". 5. **Only the sender closes a channel.** Closing from the receiver side panics on the next send. 6. **Specify channel direction** (`chan<-`, `<-chan`) at function boundaries — the compiler catches misuse. 7. **Always include `ctx.Done()` in `select`.** Without it, the goroutine cannot be cancelled. 8. **Test for leaks** with [`go.uber.org/goleak`](https://pkg.go.dev/go.uber.org/goleak).
| Need | Use | Why | |---|---|---| | Pass a value from producer to consumer | Channel | Transfers ownership explicitly | | Wait for N fire-and-forget goroutines | `sync.WaitGroup` (Go 1.25: `wg.Go`) | No error needed | | Wait + collect first error + cancel siblings | `errgroup.WithContext` | Structured failure | | Bound concurrency (worker pool) | `errgroup.SetLimit(n)` | Replaces hand-rolled pools | | Protect a shared field | `sync.Mutex` / `sync.RWMutex` | Short critical section | | Counter / flag | typed `sync/atomic` (`atomic.Int64`, `atomic.Bool`) | Lock-free, type-safe | | Read-heavy concurrent map | `sync.Map` | Concurrent map read/write otherwise crashes | | One-shot init | `sync.Once` (or `OnceFunc`/`OnceValue` in 1.21+) | Idempotent setup | | Deduplicate concurrent calls | `x/sync/singleflight` | Cache stampede prevention |
> Read [references/sync-primitives.md](references/sync-primitives.md) when picking between mutex, atomic, `sync.Map`, `sync.Pool`, or `singleflight`, or when designing the field layout of a struct that protects shared state.
// Good: bounded WaitGroup, deterministic exit
var wg sync.WaitGroup
for item := range queue {
wg.Add(1)
go func(it Item) { defer wg.Done(); process(ctx, it) }(item)
}
wg.Wait()
// Bad: no stop signal, no wait — classic leak
go func() { for { flush(); time.Sleep(delay) } }()Go 1.25+ exposes `wg.Go(fn)` which folds `Add`/`Done` into one call. Always call `wg.Add` **before** `go` — otherwise `wg.Wait` may return before the goroutine even starts.
`errgroup.WithContext` is the right default when sibling goroutines should cancel each other on the first failure:
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(8)
for _, url := range urls {
g.Go(func() error { return fetch(ctx, url) })
}
if err := g.Wait(); err != nil {
return fmt.Errorf("fetching urls: %w", err)
}`g.Wait` returns the first non-nil error; `ctx` is cancelled as soon as any worker fails. See [references/errgroup-and-pools.md](references/errgroup-and-pools.md).
func produce(out chan<- int) { /* send-only */ }
func consume(in <-chan int) { /* receive-only */ }
func transform(in <-chan int, out chan<- int) { /* ownership crosses */ }**Buffer size is `0` or `1`.** Anything larger must be justified (what bounds it under load, what happens when writers block).
In every long-running `select`, include `<-ctx.Done()`. Avoid `time.After` in hot loops — it allocates a timer per iteration; hoist a `time.NewTimer` and `Reset` it instead.
> Read [references/channels-and-select.md](references/channels-and-select.md) when implementing pipelines, fan-in/fan-out, broadcast via `close`, or non-blocking sends with `default`.
The zero value of `sync.Mutex`/`RWMutex` is valid — almost never use a pointer. Do not embed mutexes; keep them as an unexported `mu` field so `Lock`/`Unlock` aren't public API. Keep critical sections short; never hold a lock across I/O. Prefer typed atomics (`atomic.Bool`, `atomic.Int64`) over raw `sync/atomic` on `int32`/`int64` fields.
Wire `go.uber.org/goleak` into every package that spawns goroutines (`goleak.VerifyTestMain(m)` or `defer goleak.VerifyNone(t)`). For timer-dependent tests, use `testing/synctest` (Go 1.25+) so synthetic time advances deterministically. Go 1.26 adds an experimental `goroutineleak` pprof profile for production diagnosis — it is not a substitute for `goleak` in tests. See [references/leaks-and-synctest.md](references/leaks-and-synctest.md).
| Anti-pattern | Why it hurts | Do this instead | |---|---|---| | Fire-and-forget `go func()` with no signal | Leaks on shutdown; can outlive its inputs | Pass `ctx`, use `errgroup`, or own a done channel | | Closing a channel from the receiver | Panics on the next send | Only the sender closes | | `time.After` in a hot loop | Allocates a timer per iteration | `time.NewTimer` + `Reset` | | `select` without `ctx.Done()` | Cannot be ca
26 production-grade Go skills for Claude Code, Gemini CLI, and opencode. Battle-tested patterns from the Go community — codified as triggerable AI skills.
Repo: muratmirgun/gophers
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase, Repository, Delivery layers, inward…
Invoke this skill to systematically review a Go change against community style standards before merging. Walks the diff topic by topic — formatting, errors,…
Use when writing or reviewing Go code for clarity, formatting, control flow, variable declarations, switch usage, and function design. Covers the priority…
Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation, request-scoped values,…
Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization, guard clauses, early returns,…
Use when choosing or operating on Go slices, maps, arrays, strings, or container/* types — including slice internals, capacity growth, preallocation, map…