go-context · eduardo-sl — Claude Code skill: context.Context done right in Go
A skill that makes your coding agent handle context.Context in Go correctly: cancellation, timeouts, values and the usual anti-patterns.
npx skills add eduardo-sl/go-agent-skills --skill go-context -a claude-codeWhat go-context does
The skill spells out how context.Context should travel through Go code: first parameter, never inside a struct, never nil, and cancel always deferred. Separate sections cover cancellation inside loops, timeout budgets, value keys, and context in HTTP handlers and tests.
It ships as SKILL.md plus reference files the agent pulls in only when needed: references/timeout-budgets.md on splitting a parent's remaining budget and reading ctx.Deadline(), references/values.md on key types, references/http-and-testing.md on r.Context() and cancellation tests, references/cause-and-detach.md on WithCancelCause, WithoutCancel and AfterFunc. The agent gets Read, Edit, Write, Glob, Grep and permission to run go and gofmt.
The Go toolchain is required. The skill marks itself as a community default, so a project rule or agent config that explicitly overrides go-context wins. Work outside its scope — concurrency, HTTP middleware, error handling — belongs to other skills in the same collection.
What it helps with
- put context first in every function signature
- defer cancel so goroutines and timers do not leak
- split a timeout budget across nested calls
- honour cancellation between loop iterations
- move context values behind an unexported key type
Example prompts for the agent
- Review context.Context usage in
service/user.goand list the anti-patterns - Add a 5 second timeout to
FetchUserwith a deferred cancel - This loop ignores cancellation — why, and how do I fix it?
How to install go-context
1Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-context -a claude-code2Manually: put the files in place
~/.claude/skills/go-context/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/go-context/
1Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-context -a codex2Manually: put the files in place
.agents/skills/go-context/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-context -a cursor2Manually: put the files in place
.cursor/skills/go-context/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install eduardo-sl/go-agent-skills go-context2Manually: put the files in place
.github/skills/go-context/SKILL.md1Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-context -a gemini-cli2Platform CLI
gemini skills install https://github.com/eduardo-sl/go-agent-skills --path 'skills/(code-quality)/go-context'Manually: put the files in place
.devin/skills/go-context/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-context -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about go-context
How do I install go-context in Claude Code?
Run npx skills add eduardo-sl/go-agent-skills --skill go-context -a claude-code. The skill lands in ~/.claude/skills/go-context/SKILL.md.
What does go-context do?
It encodes the rules for using context.Context in Go: propagation, cancellation, timeouts, deadlines, values and common mistakes. The agent applies them while editing code or answering context questions.
Does it cover concurrency and error handling too?
No. Concurrency beyond context, HTTP middleware and error handling live in go-concurrency-review, go-api-design and go-error-handling. A project rule that explicitly supersedes go-context takes precedence.
Source text
SKILL.md · MIT
---
name: go-context
description: >
Correct usage of context.Context in Go: propagation, cancellation,
timeouts, deadlines, values, and common anti-patterns. Use when: "context
usage", "context.Context", "context cancellation", "timeout",
"context.WithTimeout", "context.WithCancel", "context values", "context
propagation".
Not for: concurrency beyond context (go-concurrency-review), HTTP
middleware (go-api-design), error handling (go-error-handling).
user-invocable: true
license: MIT
compatibility: Designed for Claude Code or similar AI coding agents working on Go projects. Requires the Go toolchain.
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(gofmt:*)
metadata:
author: eduardo-sl
version: "1.3.2"
---
# Go Context
`context.Context` controls cancellation, deadlines, and request-scoped values
across API boundaries. Misusing it causes goroutine leaks, orphaned work,
and subtle production bugs.
> Community default. A project skill or agent-config rule that explicitly
> supersedes `go-context` takes precedence over this one.
Detailed reference material, loaded on demand:
- `references/timeout-budgets.md` — dividing a parent's remaining budget,
reading `ctx.Deadline()`.
- `references/values.md` — context key types, accessors, collision traps.
- `references/http-and-testing.md` — request context in handlers and
middleware, and cancellation tests.
- `references/cause-and-detach.md` — `WithCancelCause`, `WithTimeoutCause`,
`WithoutCancel`, `AfterFunc`.
Read a reference file only when the section below is not enough.
## 1. Core Rules
### Context is always the first parameter:
```go
// ✅ Good — context is first
func GetUser(ctx context.Context, id string) (*User, error)
func (s *Service) Process(ctx context.Context, req Request) error
// ❌ Bad — context buried in the middle or end
func GetUser(id string, ctx context.Context) (*User, error)
func Process(req Request, ctx context.Context) error
```
### NEVER store context in a struct:
```go
// ❌ Bad — context stored in struct
type Server struct {
ctx context.Context // NEVER do this
cancel context.CancelFunc
}
// ✅ Good — pass context through method parameters
func (s *Server) Shutdown(ctx context.Context) error {
return s.httpServer.Shutdown(ctx)
}
```
Context represents the lifetime of a single operation, not the lifetime of an object.
### NEVER pass nil context:
```go
// ❌ Bad
doSomething(nil, data)
// ✅ Good — use context.TODO() if unsure which context to use
doSomething(context.TODO(), data)
// ✅ Good — use context.Background() for top-level/main
doSomething(context.Background(), data)
```
## 2. Cancellation
### Always defer cancel:
```go
// ✅ Good — cancel called even if operation succeeds
ctx, cancel := context.WithCancel(parentCtx)
defer cancel()
result, err := longOperation(ctx)
```
Failing to call cancel leaks resources (timers, goroutines) until the parent
context is cancelled.
### Use WithCancel for manual cancellation:
```go
func (s *Supervisor) Run(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return s.runWorkerA(ctx) })
g.Go(func() error { return s.runWorkerB(ctx) })
// If any worker returns an error, errgroup cancels ctx,
// which signals all other workers to stop.
return g.Wait()
}
```
### Check context cancellation in loops:
```go
// ✅ Good — respects cancellation
func processItems(ctx context.Context, items []Item) error {
for _, item := range items {
if err := ctx.Err(); err != nil {
return fmt.Errorf("processing cancelled: %w", err)
}
if err := process(ctx, item); err != nil {
return fmt.Errorf("process item %s: %w", item.ID, err)
}
}
return nil
}
// ❌ Bad — runs to completion even if cancelled
func processItems(ctx context.Context, items []Item) error {
for _, item := range items {
process(ctx, item) // ignores ctx cancellation between items
}
return nil
}
```
## 3. Timeouts and Deadlines
### WithTimeout for duration-based limits:
```go
func (c *Client) FetchUser(ctx context.Context, id string) (*User, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.url+"/users/"+id, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch user %s: %w", id, err)
}
defer resp.Body.Close()
// ...
}
```
### WithDeadline for absolute time limits:
```go
// Use when coordinating with external deadlines (SLAs, cron windows)
deadline := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
ctx, cancel := context.WithDeadline(ctx, deadline)
defer cancel()
```
### Timeout budgets
A child timeout spends part of the parent's budget; it never extends it. A
5s DB call and a 10s API call inside a 30s request handler are a budget. A
60s child under a parent with 5s left silently fires at the parent's
deadline, which reads as a bug at 3am.
Before starting work that cannot be interrupted, read the remaining budget
with `ctx.Deadline()` and fail fast when it is too short.
Examples in `references/timeout-budgets.md`.
## 4. Context Values
Store only request-scoped metadata: request ID, trace or span ID, the
authenticated user, a request-scoped logger. Never a database connection,
configuration, or anything the function could take as an explicit parameter.
Keys must be an unexported type, never a string — a string key can be
overwritten by any other package in the process. Export accessor functions
so callers never type-assert on `ctx.Value` themselves.
Examples in `references/values.md`.
## 5. Context in HTTP Handlers and Tests
Handlers take `r.Context()` and pass it downstream. A cancelled request means
the client disconnected: return without writing a response. Middleware
attaches values with `r.WithContext(ctx)`.
Tests derive contexts from `t.Context()` (Go 1.24+), not
`context.Background()`, wrap the call under test in `context.WithTimeout` so
a hang fails the test instead of blocking the suite, and assert
`errors.Is(err, context.Canceled)` to prove cancellation is honoured.
Examples in `references/http-and-testing.md`.
## 6. Causes, Detached Work, and AfterFunc
| Need | API (Go version) |
|---|---|
| Record *why* work was cancelled | `WithCancelCause` + `context.Cause` (1.20) |
| Say which timeout fired | `WithTimeoutCause`, `WithDeadlineCause` (1.21) |
| Finish work after the request returns, keeping its values | `WithoutCancel` + your own timeout (1.21) |
| Unblock a call that takes no context | `AfterFunc` (1.21) |
Detached work always gets its own timeout. `context.Background()` would
also outlive the request, but it drops the trace ID. Examples:
`references/cause-and-detach.md`.
## 7. context.Background() vs context.TODO()
| Function | When to use |
|---|---|
| `context.Background()` | Top-level: `main()`, `init()`. Intentional root context. Tests use `t.Context()` (Go 1.24+). |
| `context.TODO()` | Placeholder when you don't know which context to use yet. Signals "this needs to be fixed". |
`context.TODO()` is a code smell in production code — replace it before shipping.
## Verification Checklist
1. `context.Context` is the first parameter in all functions that accept it
2. No context stored in struct fields
3. `defer cancel()` called immediately after `WithCancel`, `WithTimeout`, `WithDeadline`
4. Long loops check `ctx.Err()` between iterations
5. Child timeouts don't exceed parent timeout budget
6. Context values use unexported key types with accessor functions
7. Only request-scoped metadata stored in context values (not configs, connections)
8. HTTP handlers use `r.Context()` and pass it downstream
9. No `nil` context passed — use `context.TODO()` or `context.Background()`
10. Tests use `context.WithTimeout` to prevent hanging
11. Work that outlives a request uses `WithoutCancel` plus its own timeout, not `context.Background()`
© eduardo-sl · MIT · eduardo-sl/go-agent-skills
Similar skills
All skills for Programming languages →go-context by other authors comes first.