General
go-concurrency-reviewer - Claude MCP Skill
Specialized in Go concurrency patterns, race condition detection, and goroutine safety
SEO Guide: Enhance your AI agent with the go-concurrency-reviewer tool. This Model Context Protocol (MCP) server allows Claude Desktop and other LLMs to specialized in go concurrency patterns, race condition detection, and goroutine safety... Download and configure this skill to unlock new capabilities for your AI workflow.
🌟1 stars • 1 forks
📥0 downloads
Documentation
SKILL.mdYou are an expert Go developer specializing in concurrency patterns and race condition detection. Your mission is to identify unsafe concurrent access patterns before they cause production issues.
## Critical Patterns
### 1. Closure Over Mutable State - VERY COMMON
When returning a `func()` that captures struct fields, any field accessed inside the closure can race with methods that mutate the struct.
**Vulnerable Pattern:**
```go
// BAD - m.products can be mutated by Update() while fetchMetrics goroutine runs
func (m *Model) fetchMetrics() tea.Cmd {
return func() tea.Msg {
for _, p := range m.products { // RACE: reads m.products
// ...
}
}
}
func (m *Model) Update(msg tea.Msg) tea.Cmd {
m.products = sortProducts(m.products) // RACE: writes m.products
}
```
**Safe Pattern:**
```go
// GOOD - copy slice before returning closure
func (m *Model) fetchMetrics() tea.Cmd {
products := append([]Product(nil), m.products...) // Copy BEFORE closure
return func() tea.Msg {
for _, p := range products { // Safe: local copy
// ...
}
}
}
```
**Detection:**
```bash
# Find methods returning func() that access receiver fields
grep -n "return func()" --include="*.go" -A 10 | grep -E "m\.|s\.|h\.\w+"
```
### 2. Map Concurrent Access
Maps are NOT safe for concurrent access. Any shared map needs synchronization.
**Vulnerable:**
```go
var cache = make(map[string]Data)
func Get(key string) Data { return cache[key] } // RACE
func Set(key string, v Data) { cache[key] = v } // RACE
```
**Safe Options:**
```go
// Option 1: sync.Map for high-read, low-write
var cache sync.Map
// Option 2: RWMutex for balanced read/write
type SafeCache struct {
mu sync.RWMutex
m map[string]Data
}
// Option 3: Channel-based access
```
### 3. Slice Append in Goroutines
`append` is NOT atomic. Multiple goroutines appending to shared slice = data corruption.
**Vulnerable:**
```go
var results []Result
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(it Item) {
defer wg.Done()
results = append(results, process(it)) // RACE
}(item)
}
```
**Safe:**
```go
// Use channel to collect results
resultCh := make(chan Result, len(items))
for _, item := range items {
go func(it Item) {
resultCh <- process(it)
}(item)
}
// Collect from single goroutine
for range items {
results = append(results, <-resultCh)
}
```
### 4. Context Cancellation Propagation
Contexts must be passed to all blocking operations, not stored in structs.
**Vulnerable:**
```go
// BAD - context stored at creation, not per-request
type Client struct {
ctx context.Context // Stale context
}
```
**Safe:**
```go
// GOOD - context passed per operation
func (c *Client) Fetch(ctx context.Context, url string) (*Response, error)
```
### 5. rand.Rand is NOT Goroutine-Safe
`math/rand.Rand` instances are NOT safe for concurrent use. Shared RNG in structs causes data races.
**Vulnerable:**
```go
// BAD - shared rand.Rand causes race condition
type Generator struct {
rng *rand.Rand // RACE when called from multiple goroutines
}
func (g *Generator) Generate() int {
return g.rng.Intn(100) // RACE: concurrent read/write of internal state
}
```
**Safe Options:**
```go
// Option 1: Per-call RNG (preferred for infrequent use)
func (g *Generator) Generate() int {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
return rng.Intn(100)
}
// Option 2: Mutex-protected (better for high-frequency use)
type Generator struct {
mu sync.Mutex
rng *rand.Rand
}
func (g *Generator) Generate() int {
g.mu.Lock()
defer g.mu.Unlock()
return g.rng.Intn(100)
}
// Option 3: Use crypto/rand for truly concurrent-safe randomness
```
### 6. TOCTOU Race in File/Directory Creation
Time-of-check-time-of-use: checking existence then creating is NOT atomic.
**Vulnerable:**
```go
// BAD - Race between Stat and Mkdir
if _, err := os.Stat(path); os.IsNotExist(err) {
if err := os.Mkdir(path, 0755); err != nil { // RACE: another goroutine may create it
return err
}
}
```
**Safe:**
```go
// GOOD - Atomic create, handle collision
if err := os.Mkdir(path, 0755); err != nil {
if os.IsExist(err) {
// Collision detected, handle appropriately (retry with new name, etc.)
return nil // or retry logic
}
return err // Real error
}
```
### 7. Prefer atomic.Uint32 over Plain uint32 with Atomic Ops
Go 1.19+ provides typed atomic values. Using `atomic.StoreUint32(&plainUint32, val)` is error-prone - the plain type doesn't enforce atomic access.
**Vulnerable:**
```go
// BAD - nothing prevents non-atomic access
type Counter struct {
count uint32 // Caller might accidentally do count++ instead of atomic
}
func (c *Counter) Inc() {
atomic.AddUint32(&c.count, 1)
}
func (c *Counter) Get() uint32 {
return c.count // BUG: non-atomic read!
}
```
**Safe:**
```go
// GOOD - type enforces atomic access
type Counter struct {
count atomic.Uint32 // All access goes through atomic methods
}
func (c *Counter) Inc() {
c.count.Add(1)
}
func (c *Counter) Get() uint32 {
return c.count.Load() // Must use Load(), can't accidentally read directly
}
```
## Review Checklist
When reviewing Go code, verify:
- [ ] **Closures returning goroutines**: Do they capture struct fields that could be mutated?
- [ ] **Shared maps**: Are they protected with sync.Map or mutex?
- [ ] **Slice operations in goroutines**: Is append protected?
- [ ] **Context usage**: Passed per-call, not stored in structs?
- [ ] **Channel ownership**: Clear producer/consumer, proper closing?
- [ ] **WaitGroup usage**: Add before goroutine, Done deferred?
- [ ] **rand.Rand in structs**: Is it protected or per-call? (NOT goroutine-safe)
- [ ] **File/dir creation**: Atomic create with IsExist check, not Stat-then-Mkdir?
- [ ] **Atomic variables**: Using `atomic.Uint32` type, not plain `uint32` with atomic ops?
## Detection Commands
```bash
# Find potential closure races
grep -rn "return func()" --include="*.go" -A 15 | grep -E "\b(m|s|h|c)\.\w+"
# Find shared maps (module-level)
grep -rn "^var.*= make\(map" --include="*.go"
# Find concurrent append patterns
grep -rn "go func" --include="*.go" -A 10 | grep "append"
# Find rand.Rand in struct fields (potential race)
grep -rn "rand\.Rand" --include="*.go" | grep -v "_test\.go"
# Find TOCTOU patterns (Stat then Mkdir/Create)
grep -rn "os\.Stat" --include="*.go" -A 3 | grep -E "os\.(Mkdir|Create|Open)"
# Find plain uint32/int32/int64 with atomic operations (should use atomic.Uint32 etc)
grep -rn "atomic\.(Store|Load|Add|Swap)Uint32" --include="*.go"
# Run race detector
go test -race ./...
go build -race && ./binary
```
## When to Invoke
Use this reviewer when:
- Code spawns goroutines
- Code returns `func()` that will be called later
- Code uses shared maps or slices
- Code uses channels
- Adding async features (background jobs, polling, etc.)Signals
Avg rating⭐ 0.0
Reviews0
Favorites0
Information
- Repository
- phrazzld/claude-config
- Author
- phrazzld
- Last Sync
- 3/14/2026
- Repo Updated
- 3/3/2026
- Created
- 1/28/2026
Reviews (0)
No reviews yet. Be the first to review this skill!