← ClaudeAtlas

go-concurrencylisted

Use when writing or reviewing Go code that starts goroutines, passes context, uses channels, sync primitives, worker pools, or errgroup — goroutine lifecycle and leaks, context propagation and cancellation, timeouts, and safe shared state. Triggers on `go func`, `context.Context`, `sync.`, `select`, or reports of leaks, races, and hangs.
Markuysa/agent-skills · ★ 0 · AI & Automation · score 70
Install: claude install-skill Markuysa/agent-skills
# Go concurrency Every goroutine needs an owner who knows when it stops. Concurrency bugs in production are almost never exotic — they are goroutines nobody stops, contexts nobody passes, and errors nobody reads. ## Goroutine lifecycle Before writing `go func()`, answer three questions: 1. **Who stops it?** A context, a closed channel, or a returning loop. 2. **Who reads its error?** An `errgroup`, an error channel, or it panics the process. 3. **Who waits for it?** Shutdown must not exit while it's mid-write. If any answer is "nobody", you are writing a leak. ```go // bad — unowned, unstoppable, error vanishes go processAll(items) // good — bounded, cancellable, errors surface g, ctx := errgroup.WithContext(ctx) g.SetLimit(runtime.GOMAXPROCS(0)) for _, item := range items { g.Go(func() error { return process(ctx, item) }) } if err := g.Wait(); err != nil { return fmt.Errorf("process items: %w", err) } ``` `errgroup.WithContext` cancels the derived context on the first error, so siblings stop instead of finishing work whose result is already discarded. Use `SetLimit` to bound concurrency — unbounded fan-out over a slice of unknown size is how you exhaust connection pools. ## Context - Pass `ctx` as the **first parameter**, always named `ctx`. Never store it in a struct field (the one accepted exception is a request-scoped struct that lives and dies with the request). - Only the function that creates a cancel must call it: `defer cancel()`, al