
Golang Concurrency
Install this when a solo builder wants their agent to write or fix Go worker loops, channels, and timeouts without classic concurrency footguns.
Overview
golang-concurrency is an agent skill for the Build phase that steers Go goroutine, channel, and timer code away from common leaks, races, and allocation traps.
Install
npx skills add https://github.com/samber/cc-skills-golang --skill golang-concurrencyWhat is this skill?
- Rejects time.After inside for/select loops; uses time.NewTimer, Reset, Stop, and Go 1.23-aware stale-drain guidance
- Enforces sender-closes-channel: receivers signal completion without closing producer-owned channels
- Eval-backed traps for inactivity-timeout workers and producer/consumer shutdown designs
- Assertion-style checks agents can mirror when generating or reviewing concurrent Go
- Focused on production worker patterns, not abstract concurrency tutorials
- Eval readme documents 2 named concurrency trap scenarios (time-after-in-select-loop, channel-closing-ownership).
- First eval scenario defines 5 numbered assertions for timer reuse and cleanup.
Adoption & trust: 4k installs on skills.sh; 2k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your agent keeps suggesting time.After in every select iteration or lets the consumer close the channel, which is fragile in real Go workers.
Who is it for?
Solo builders using Claude Code, Cursor, or Codex on Go services with channel-driven workers, timeouts, or producer/consumer goroutines.
Skip if: Non-Go stacks, greenfield concurrency learning without code, or teams that already enforce the same rules exclusively via CI linters and never use agents to write concurrent Go.
When should I use this skill?
When writing, refactoring, or reviewing Go code that uses goroutines, channels, select loops, or inactivity timeouts.
What do I get? / Deliverables
You get Go concurrency snippets that reuse timers correctly, respect sender-close rules, and align with the skill’s eval assertions before you merge.
- Select loops that reuse timers with Reset and Stop instead of loop-scoped time.After
- Producer/consumer layouts where only the sender closes the channel
- Concurrency snippets aligned to the skill’s eval assertion checklist
Recommended Skills
Journey fit
Go concurrency fixes land while implementing services, workers, and channel-driven backends—not during idea or launch work. Channel selects, goroutine lifecycles, and timer reuse are backend implementation concerns in Go codebases.
How it compares
Procedural concurrency guardrails for agent-written Go—not a hosted MCP server or a full staticcheck replacement.
Common Questions / FAQ
Who is golang-concurrency for?
It is for indie and solo developers shipping Go backends who rely on AI agents to draft workers, selects, and channel plumbing and want those drafts to match senior Go review habits.
When should I use golang-concurrency?
Use it during Build backend work when implementing timeout loops, job consumers, or graceful shutdown; also handy in Ship review when auditing agent-generated concurrent code before release.
Is golang-concurrency safe to install?
Treat it like any third-party agent skill: review the Security Audits panel on this Prism page and your org policy before enabling it in repos with secrets or production deploy keys.
SKILL.md
READMESKILL.md - Golang Concurrency
[ { "id": 1, "name": "time-after-in-select-loop", "description": "Tests whether the model avoids time.After inside a select loop and uses time.NewTimer with Reset instead", "prompt": "I have a Go worker that reads from a channel and needs a 5-second inactivity timeout. If nothing arrives for 5 seconds, it should log and continue waiting. Write the select loop.", "trap": "Without the skill, the model uses time.After(5*time.Second) inside the for/select loop, creating repeated timer allocations/churn instead of reusing a timer", "assertions": [ {"id": "1.1", "text": "Does NOT use time.After inside the loop body"}, {"id": "1.2", "text": "Creates time.NewTimer (or time.NewTicker) outside the loop"}, {"id": "1.3", "text": "Calls timer.Reset() after handling a message or timeout"}, {"id": "1.4", "text": "Calls timer.Stop() (or defers it) to clean up the timer"}, {"id": "1.5", "text": "For Go 1.23+, does not use the old stale-drain pattern; for Go <1.23 compatibility, drains before Reset when Stop reports a possible pending value"} ] }, { "id": 2, "name": "channel-closing-ownership", "description": "Tests whether the model follows the rule that only the sender closes a channel, not the receiver", "prompt": "I have a producer goroutine that sends items to a channel and a consumer goroutine that reads from it. The consumer knows when it has received enough items and wants to signal completion. How should I close the channel to stop the producer?", "trap": "Without the skill, the model closes the channel from the consumer side, which panics if the producer writes after close", "assertions": [ {"id": "2.1", "text": "Does NOT close the channel from the consumer/receiver side"}, {"id": "2.2", "text": "Uses a separate signaling mechanism (done channel, context cancellation, or similar) for the consumer to tell the producer to stop"}, {"id": "2.3", "text": "The producer is the one that closes the data channel (or it is closed by the channel creator/sender)"}, {"id": "2.4", "text": "Explains the panic risk of closing a channel from the receiver side"}, {"id": "2.5", "text": "The producer selects on the stop signal alongside its send operation"} ] }, { "id": 3, "name": "waitgroup-add-placement", "description": "wg.Add before go statement; wg.Add(len(urls)) is preferred over per-goroutine Add(1) when count is known", "prompt": "Review this Go code and identify all WaitGroup issues:\n\n```go\nfunc ProcessURLs(urls []string) []Result {\n var wg sync.WaitGroup\n results := make([]Result, len(urls))\n\n for i, url := range urls {\n go func(i int, url string) {\n wg.Add(1)\n defer wg.Done()\n results[i] = fetch(url)\n }(i, url)\n }\n\n wg.Wait()\n return results\n}\n```\n\nFix all issues. Also, is there a more efficient way to add to the WaitGroup when the number of goroutines is known upfront?", "trap": "The most obvious bug (wg.Add inside goroutine) is well-known. The model should also catch the second issue: wg.Add(len(urls)) before the loop is more efficient than N separate wg.Add(1) calls (single atomic update vs N atomic updates). The model likely fixes the placement but may not suggest the batch Add optimization.", "assertions": [ {"id": "3.1", "text": "Moves wg.Add(1) BEFORE the go statement — wg.Add inside the goroutine races with wg.Wait(), which can return before all goroutines have registered"}, {"id": "3.2", "text": "Suggests using wg.Add(len(urls)) before the loop as a more efficient alternative — a single atomic operation instead of N separate wg.Add(1) calls in the loop"}, {"id": "3.3", "text": "Keeps defer wg.Done() inside the goroutine"}, {"id": "3.4", "text": "Notes the race condition: wg.Wait() could return before some goroutines have called wg.Add(1), causing ProcessURLs to return with goroutin