go-concurrency — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited go-concurrency (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
sync.WaitGroup, errgroup.Group, or an explicit done channel.Start/Stop/Shutdown so callers control the lifecycle.sync.Mutex only when the problem is genuinely "protect a shared field".chan<-, <-chan) at function boundaries — the compiler catches misuse.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 when picking between mutex, atomic,sync.Map,sync.Pool, orsingleflight, 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.
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 when implementing pipelines, fan-in/fan-out, broadcast viaclose, or non-blocking sends withdefault.
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.
| 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 cancelled | Always include the cancel case |
wg.Add(1) inside the goroutine | Wait may return before Add runs | Add before go, or use wg.Go (Go 1.25+) |
| Buffered channel sized "to be safe" | Hides backpressure, masks bugs | Size 0 or 1; justify anything larger |
Concurrent read+write on map | Hard runtime crash, not a race warning | sync.Map or sync.RWMutex + map |
| Mutex held across I/O / RPC | Serializes the whole service | Copy what you need under the lock; release before the call |
| Sending a pointer through a channel | Re-introduces shared memory | Send a copy or an immutable value |
Forgetting -race in CI | Races ship to prod | go test -race ./... always |
Before finishing a concurrency change:
go has a documented exit (ctx, done channel, or bounded loop)select has a <-ctx.Done() casewg.Add is called before go, or wg.Go is used (Go 1.25+)for v := range ch or v, ok := <-chgo test -race ./... is cleangoleak.VerifyTestMain or per-test VerifyNonesync.Map/Pool/Once/singleflighterrgroup, SetLimit, worker pools, fan-out/fan-ingoleak, testing/synctest, Go 1.26 experimental leak profile~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.