# Go Concurrency

> Use when writing or reviewing Go concurrency — goroutines, channels, errgroup, context cancellation, or goroutine leaks. Not for sequential idioms (go-core-idioms).

- Skill: `fusengine/go-concurrency` (Agent Skill, multi-file: 8 files)
- Install (CLI): `npx skillmds@latest add fusengine/go-concurrency`
- Raw SKILL.md: https://api.skillmd.com/api/skills/fusengine/go-concurrency/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: fusengine (https://skillmd.com/u/fusengine)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/fusengine/go-concurrency

---


<objective>
Covers Go concurrency for Go 1.26: goroutines and channels, golang.org/x/sync/errgroup,
context propagation and cancellation, sync.WaitGroup vs channels, the -race detector,
and diagnosing goroutine leaks (including the 1.26 goroutineleak profile). Does not
cover sequential error handling, slog, generics, or interface style (see
go-core-idioms), non-Go languages, or framework-specific code.
</objective>

# Go Concurrency

Goroutines, channels, `context`, and `errgroup` for Go 1.26 — plus the number-one
documented pitfall: **leaking goroutines on an unbuffered channel + early return.**

## Agent Workflow (MANDATORY)

Before ANY implementation, spawn 3 agents in parallel, one `Agent` call each with a `name`:

1. **fuse-ai-pilot:explore-codebase** - Map existing goroutine/channel/context usage
2. **fuse-ai-pilot:research-expert** - Verify errgroup/context docs via Context7/Exa
3. **mcp__context7__query-docs** - Confirm `golang.org/x/sync/errgroup` signatures

After implementation, run **fuse-ai-pilot:sniper** for validation, and run tests
with `go test -race ./...`.

---

## Overview

| Feature | Description |
|---------|-------------|
| **Goroutines & channels** | Lightweight concurrency + typed communication |
| **errgroup** | Parallelism + error aggregation + context cancellation |
| **context** | First param, propagated strictly, carries cancellation/deadline |
| **WaitGroup vs channels** | Counting-only vs result/error passing |
| **Race detector** | `-race` in tests/CI to catch data races |
| **Leak profile (1.26)** | `GOEXPERIMENT=goroutineleakprofile` / `/debug/pprof/goroutineleak` |

---

## Critical Rules

1. **`context.Context` is the first parameter** - named `ctx`, never stored in a struct
2. **Every started goroutine must be able to exit** - or it leaks (see rule 4)
3. **Prefer `errgroup` for fan-out with errors** - it handles wait + first error + cancel
4. **Unbuffered channel + early return = leak** - senders block forever; buffer or drain
5. **Test with `-race`** - a passing test without `-race` proves nothing about races

---

## Architecture

```
internal/
├── fetch/
│   ├── fetch.go        # errgroup.WithContext fan-out, bounded by SetLimit
│   └── worker.go       # worker pool: fixed goroutines drain a jobs channel
└── pipeline/
    └── stage.go        # ctx-cancellable stages, buffered hand-off channels
```

→ See [errgroup-patterns.md](references/templates/errgroup-patterns.md) for full example

---

## Reference Guide

### Concepts

| Topic | Reference | When to Consult |
|-------|-----------|-----------------|
| **Goroutines & channels** | [goroutines-channels.md](references/goroutines-channels.md) | Buffered vs not, select, WaitGroup vs channels |
| **errgroup** | [errgroup.md](references/errgroup.md) | Fan-out, error aggregation, SetLimit, TryGo |
| **context** | [context-propagation.md](references/context-propagation.md) | Cancellation, deadlines, propagation rules |
| **Goroutine leaks** | [goroutine-leaks.md](references/goroutine-leaks.md) | The #1 pitfall + the 1.26 leak profile |

### Templates

| Template | When to Use |
|----------|-------------|
| [errgroup-patterns.md](references/templates/errgroup-patterns.md) | Bounded parallel work with error handling |
| [worker-pool.md](references/templates/worker-pool.md) | Fixed workers draining a job queue |

---

## Quick Reference

### Parallel work with errgroup

```go
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(8) // bound concurrency
for _, u := range urls {
    g.Go(func() error { return fetch(ctx, u) })
}
if err := g.Wait(); err != nil { // first non-nil error; cancels ctx
    return err
}
```

→ See [errgroup.md](references/errgroup.md)

### Avoid the leak (buffer so senders never block)

```go
ch := make(chan result, len(items)) // buffered → early return can't strand senders
```

→ See [goroutine-leaks.md](references/goroutine-leaks.md)

---

## Best Practices

### DO
- Pass `ctx` first and thread it through every blocking call
- Reach for `errgroup` before hand-rolling `WaitGroup` + error channels
- Buffer result channels to the number of senders, or fully drain them
- Run `go test -race`; try `GOEXPERIMENT=goroutineleakprofile` in CI (1.26)

### DON'T
- Return early from a fan-out while goroutines still block on an unbuffered channel
- Store a `context.Context` in a struct field
- Use a bare `sync.WaitGroup` when goroutines return errors (use `errgroup`)
- Assume tests are race-free without the `-race` flag

