Generated banner for Golang Context
Generated logo for Golang Context

Golang Context

Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe

  • Skill
  • Guidelines
  • Software development
  • MIT
  • No issues found

From the author

Idiomatic context.Context usage in Golang — propagation through API boundaries, cancellation, timeouts and deadlines, request-scoped values, context.WithoutCancel for background work outliving requests. Apply when designing context propagation across layers, debugging leaked or unexpired contexts, choosing between context.Background/TODO/WithoutCancel, or storing values in context. Not for code that merely accepts ctx as first parameter.
— the description by samber

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a comprehensive guide to using context.Context in Go applications. It covers the essential patterns for propagating cancellation signals, deadlines, and request-scoped values across API boundaries. It emphasizes the importance of maintaining a consistent context chain throughout the request lifecycle, proper resource management with cancel functions, and the correct use of context-aware APIs for HTTP and database operations.

What it does

  • Defines patterns for propagating context through the entire request lifecycle.
  • Explains how to manage cancellation, timeouts, and deadlines using context.
  • Provides guidelines for storing and retrieving request-scoped values safely.
  • Outlines the correct usage of context.Background, context.TODO, and context.WithoutCancel.
  • Demonstrates how to integrate context into HTTP servers and downstream service calls.
  • Highlights common pitfalls like context leaks and improper context creation.

When to use it

  • When designing or refactoring context propagation across application layers.
  • When debugging issues related to leaked contexts or unexpired deadlines.
  • When implementing background tasks that need to outlive the parent request.
  • When creating middleware that requires request-scoped metadata.
  • When ensuring database or HTTP client operations respect request cancellation.

Advantages

  • Ensures consistent cancellation behavior across complex call chains.
  • Prevents resource leaks by enforcing proper deferral of cancel functions.
  • Improves code maintainability through idiomatic context handling.
  • Reduces bugs related to context-aware API misuse.
  • Facilitates better observability through standardized trace context propagation.

Usage examples

“How should I pass a context to a database query in a service method?”
The agent will demonstrate using the context passed as the first parameter to the service method and passing it directly into the database's QueryContext or ExecContext method.
“I need to spawn a background task that shouldn't be cancelled when the HTTP request finishes. How do I do that?”
The agent will explain how to use context.WithoutCancel to detach the background task from the request's context while preserving other necessary values.
“What is the best way to store a user ID in a context?”
The agent will provide an example of defining an unexported custom type for the context key to prevent collisions and using context.WithValue to store the data.
“Why is my application leaking resources when using context.WithTimeout?”
The agent will identify the missing defer cancel() call and explain why it is necessary to release timer resources.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/samber/golang-context/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip golang-context.zip -d .agents/skills

Documentation by samber

Community default. A company skill that explicitly supersedes samber/cc-skills-golang@golang-context skill takes precedence.

Go context.Context Best Practices

context.Context is Go's mechanism for propagating cancellation signals, deadlines, and request-scoped values across API boundaries and between goroutines. Think of it as the "session" of a request — it ties together every operation that belongs to the same unit of work.

Best Practices Summary

  1. Propagate the same context through the entire request lifecycle: HTTP handler → service → DB → external APIs — any link that starts a fresh context keeps working after the client is gone.

  2. Take ctx as the first parameter, named ctx context.Context — the fixed position is what makes context-aware APIs recognizable at a glance and what linters check.

  3. Pass context through function parameters instead of storing it in a struct — the struct outlives the request that filled it, so later calls reuse a context that is already cancelled or belongs to someone else.

  4. Pass context.TODO() rather than a nil context — nil panics on the first Done() or Value() call, far from the caller that passed it.

  5. Call cancel() on all control-flow paths for WithCancel/WithTimeout/WithDeadline, unless ownership of the context and cancel function is explicitly returned or transferred — an uncalled cancel() keeps the child attached to its parent and leaks its timer until the parent finishes.

  6. Create context.Background() only at top-level entry points (main, init, tests). Deeper in the call chain — especially mid-request — it detaches the work from the caller's deadline and cancellation, the propagation break shown below.

  7. Use context.TODO() as a placeholder when a context is needed but none exists yet — it marks the gap for a later fix instead of hiding it behind a Background() that looks deliberate.

  8. Declare context value keys as unexported types — with a plain string key, two packages using "user" silently overwrite each other.

  9. Carry only request-scoped metadata in context values, never function parameters — values retrieved through Value() lose compile-time typing and disappear from the function signature.

  10. Use context.WithoutCancel (Go 1.21+) when spawning background work that must outlive the parent request — otherwise the handler returning cancels the audit log or cleanup just started.

Creating Contexts

SituationUse
Entry point (main, init, test)context.Background()
Function needs context but caller doesn't provide one yetcontext.TODO()
Inside an HTTP handlerr.Context()
Need cancellation controlcontext.WithCancel(parentCtx)
Need a deadline/timeoutcontext.WithTimeout(parentCtx, duration)

Context Propagation: The Core Principle

The most important rule: propagate the same context through the entire call chain. When you propagate correctly, cancelling the parent context cancels all downstream work automatically.

// ✗ Bad — creates a new context, breaking the chain
func (s *OrderService) Create(ctx context.Context, order Order) error {
    return s.db.ExecContext(context.Background(), "INSERT INTO orders ...", order.ID)
}

// ✓ Good — propagates the caller's context
func (s *OrderService) Create(ctx context.Context, order Order) error {
    return s.db.ExecContext(ctx, "INSERT INTO orders ...", order.ID)
}

Deep Dives

  • Cancellation, Timeouts & Deadlines — How cancellation propagates: WithCancel for manual cancellation, WithTimeout for automatic cancellation after a duration, WithDeadline for absolute time deadlines. Patterns for listening (<-ctx.Done()) in concurrent code, AfterFunc callbacks, and WithoutCancel for operations that must outlive their parent request (e.g., audit logs).

  • Context Values & Cross-Service Tracing — Safe context value patterns: unexported key types to prevent namespace collisions, when to use context values (request ID, user ID) vs function parameters. Trace context propagation: OpenTelemetry trace headers, correlation IDs for log aggregation, and marshaling/unmarshaling context across service boundaries.

  • Context in HTTP Servers & Service Calls — HTTP handler context: r.Context() for request-scoped cancellation, middleware integration, and propagating to services. HTTP client patterns: NewRequestWithContext, client timeouts, and retries with context awareness. Database operations: always use *Context variants (QueryContext, ExecContext) to respect deadlines.

Cross-References

  • → See the samber/cc-skills-golang@golang-concurrency skill for goroutine cancellation patterns using context

  • → See the samber/cc-skills-golang@golang-database skill for context-aware database operations (QueryContext, ExecContext)

  • → See the samber/cc-skills-golang@golang-observability skill for trace context propagation with OpenTelemetry

  • → See the samber/cc-skills-golang@golang-design-patterns skill for timeout and resilience patterns

Enforce with Linters

Many context pitfalls are caught automatically by linters: govet, staticcheck. → See the samber/cc-skills-golang@golang-lint skill for configuration and usage.

Files

evals/evals.json

[
  {
    "id": 1,
    "name": "context-background-in-handler",
    "description": "Tests whether the model propagates r.Context() instead of creating context.Background() inside an HTTP handler",
    "prompt": "Write a Go HTTP handler for GET /orders/:id that fetches an order from a database and calls an external payment service to get payment status. Both operations should be cancellable if the client disconnects. Use database/sql and net/http.",
    "trap": "Model might create context.Background() inside the handler instead of using r.Context(), breaking cancellation chain",
    "assertions": [
      {"id": "1.1", "text": "Uses r.Context() to obtain the request context, NOT context.Background() inside the handler"},
      {"id": "1.2", "text": "Passes the same context (or a derived child) to the database query (QueryRowContext or similar *Context variant)"},
      {"id": "1.3", "text": "Passes the same context to the external HTTP call via http.NewRequestWithContext"},
      {"id": "1.4", "text": "Does NOT use http.NewRequest (without context) for the external service call"},
      {"id": "1.5", "text": "Checks ctx.Err() or handles context cancellation when the client disconnects"}
    ]
  },
  {
    "id": 2,
    "name": "cancel-leak-timeout",
    "description": "Tests whether the model defers cancel() immediately after WithTimeout to prevent resource leaks",
    "prompt": "Write a Go function that retries an HTTP request up to 3 times with a 5-second timeout per attempt. Each attempt should have its own independent timeout. Return the response body as []byte or the last error.",
    "trap": "Model might forget to defer cancel() after WithTimeout, or create a single timeout for all retries instead of per-attempt",
    "assertions": [
      {"id": "2.1", "text": "Creates a new context.WithTimeout for each retry attempt (not one timeout for all retries)"},
      {"id": "2.2", "text": "Calls defer cancel() (or cancel() before next iteration) for every WithTimeout call"},
      {"id": "2.3", "text": "Does NOT discard the cancel function with _ (e.g., ctx, _ = context.WithTimeout(...))"},
      {"id": "2.4", "text": "Uses http.NewRequestWithContext to attach the per-attempt timeout context"},
      {"id": "2.5", "text": "Accepts a parent context parameter and derives timeouts from it"}
    ]
  },
  {
    "id": 3,
    "name": "context-value-key-type",
    "description": "Tests whether the model uses unexported key types for context values instead of string keys",
    "prompt": "Write Go middleware that extracts a tenant ID from the X-Tenant-ID header and makes it available to downstream handlers. Also write a helper function to retrieve the tenant ID from the context. Other packages in the codebase will import and use this helper.",
    "trap": "Model might use a plain string key like context.WithValue(ctx, \"tenant_id\", ...) which causes namespace collisions across packages",
    "assertions": [
      {"id": "3.1", "text": "Uses an unexported type for the context key (e.g., type contextKey string or type tenantKey struct{})"},
      {"id": "3.2", "text": "Does NOT use a plain string as the context key (e.g., context.WithValue(ctx, \"tenant_id\", ...))"},
      {"id": "3.3", "text": "Provides a typed getter function (e.g., TenantIDFromContext) that returns the value with proper type assertion"},
      {"id": "3.4", "text": "Provides a setter function or the middleware injects the value using the unexported key"},
      {"id": "3.5", "text": "The getter handles the case where the value is missing from the context (returns zero value + bool or error)"}
    ]
  },
  {
    "id": 4,
    "name": "context-in-struct-trap",
    "description": "Tests whether the model avoids storing context.Context in a struct field",
    "prompt": "Design a Go Worker struct that processes jobs from a channel. The worker should support graceful shutdown — when told to stop, it finishes the current job and exits. Write NewWorker, Start, and Stop methods.",
    "trap": "Model might store ctx context.Context as a struct field for shutdown signaling instead of passing it through function parameters",
    "assertions": [
      {"id": "4.1", "text": "Does NOT store context.Context as a field in the Worker struct"},
      {"id": "4.2", "text": "Passes context as a parameter to Start() or Run() method (e.g., Start(ctx context.Context))"},
      {"id": "4.3", "text": "Uses context cancellation or a done channel for graceful shutdown signaling"},
      {"id": "4.4", "text": "Listens to ctx.Done() in a select statement to detect shutdown"},
      {"id": "4.5", "text": "ctx is the first parameter where it appears, named ctx context.Context"}
    ]
  },
  {
    "id": 5,
    "name": "without-cancel-background-work",
    "description": "Tests whether the model uses context.WithoutCancel for background work that must outlive the request",
    "prompt": "Write a Go HTTP handler that processes a payment. After successfully charging the customer, it must send an audit log event to an external audit service asynchronously. The audit log MUST complete even if the client disconnects immediately after receiving the 200 response. The audit log needs the trace_id from the request context for correlation. Target Go 1.21+.",
    "trap": "Model might use context.Background() (losing trace_id) or pass r.Context() directly to the goroutine (cancelled when handler returns)",
    "assertions": [
      {"id": "5.1", "text": "Uses context.WithoutCancel to create a context for the audit goroutine"},
      {"id": "5.2", "text": "Does NOT pass r.Context() directly to the background audit goroutine (it gets cancelled when the handler returns)"},
      {"id": "5.3", "text": "Does NOT use context.Background() for the audit goroutine (that would lose trace_id and other values)"},
      {"id": "5.4", "text": "The audit goroutine preserves request-scoped values (trace_id) from the original context"},
      {"id": "5.5", "text": "Launches the audit as a separate goroutine (go keyword) so the handler can return immediately"}
    ]
  },
  {
    "id": 6,
    "name": "nested-timeout-shorter-wins",
    "description": "Tests understanding that nested timeouts always use the shorter deadline",
    "prompt": "Write a Go service method that calls two downstream services sequentially. The overall operation has a 10-second timeout. The first call (to a fast cache) should have a 2-second timeout. The second call (to a slow database) should have an 8-second timeout. If the cache is slow, the database should still get its full 8 seconds. Implement this timeout hierarchy.",
    "trap": "Model might naively nest WithTimeout(parentCtx, 8s) under a parent that already has 10s-minus-elapsed, not realizing that if the first call takes 2 seconds the parent only has 8s left — or worse, create child with longer timeout than parent remaining",
    "assertions": [
      {"id": "6.1", "text": "Creates the overall 10-second timeout from the parent context"},
      {"id": "6.2", "text": "Creates the cache timeout as a child of the overall context (so the 2s cache timeout is bounded by the 10s overall)"},
      {"id": "6.3", "text": "Acknowledges or handles that the database timeout is bounded by whatever time remains on the parent (not a fresh 8 seconds independent of the parent)"},
      {"id": "6.4", "text": "Defers cancel() for every WithTimeout call"},
      {"id": "6.5", "text": "Does NOT create independent context.Background() timeouts that bypass the overall deadline"}
    ]
  },
  {
    "id": 7,
    "name": "context-todo-vs-background",
    "description": "Tests correct usage of context.TODO() vs context.Background()",
    "prompt": "I'm refactoring a Go codebase to add context support. Many functions don't accept context yet. Write a migration plan showing how to incrementally add context.Context to this call chain: main() -> runServer() -> handleRequest() -> processOrder() -> saveToDatabase(). Some functions already accept context, others don't yet. Show intermediate steps with code.",
    "trap": "Model might use context.Background() everywhere as a placeholder. Skill teaches context.TODO() is the correct placeholder when a function needs a context but doesn't have one yet",
    "assertions": [
      {"id": "7.1", "text": "Uses context.TODO() (not context.Background()) as the temporary placeholder in functions not yet fully migrated"},
      {"id": "7.2", "text": "Uses context.Background() only at the true top level (main function or test setup)"},
      {"id": "7.3", "text": "Shows a migration path where context.TODO() is gradually replaced as callers are updated"},
      {"id": "7.4", "text": "Context is always the first parameter, named ctx context.Context"},
      {"id": "7.5", "text": "Does NOT pass nil as a context value at any point in the migration"}
    ]
  },
  {
    "id": 8,
    "name": "db-context-variants",
    "description": "db.BeginTx (not db.Begin) is required for cancellable transactions; context must flow to every db call including transaction start",
    "prompt": "Review this Go repository method and identify all context-related issues:\n\n```go\nfunc (r *UserRepository) TransferCredits(ctx context.Context, fromID, toID string, amount int) error {\n    tx, err := r.db.Begin()\n    if err != nil {\n        return fmt.Errorf(\"begin tx: %w\", err)\n    }\n    defer tx.Rollback()\n\n    _, err = tx.ExecContext(ctx, \"UPDATE users SET credits = credits - $1 WHERE id = $2\", amount, fromID)\n    if err != nil {\n        return fmt.Errorf(\"debit: %w\", err)\n    }\n\n    _, err = tx.ExecContext(ctx, \"UPDATE users SET credits = credits + $1 WHERE id = $2\", amount, toID)\n    if err != nil {\n        return fmt.Errorf(\"credit: %w\", err)\n    }\n\n    return tx.Commit()\n}\n```\n\nWhat is wrong with how context is used? Fix it.",
    "trap": "The model sees ExecContext being used and may think context usage is correct. The bug: db.Begin() starts the transaction without a context — if ctx is already cancelled when Begin() is called, the transaction still starts. Also, tx.Commit() should be tx.CommitContext(ctx) if available, but the main issue is db.BeginTx(ctx, nil) instead of db.Begin().",
    "assertions": [
      {"id": "8.1", "text": "Identifies db.Begin() as the bug — it starts a transaction ignoring the context; if ctx is cancelled before Begin() returns, the transaction still starts"},
      {"id": "8.2", "text": "Replaces db.Begin() with db.BeginTx(ctx, nil) — BeginTx respects context cancellation and won't start a transaction if ctx is already done"},
      {"id": "8.3", "text": "Keeps ExecContext calls passing ctx — these are already correct"},
      {"id": "8.4", "text": "Does not add a redundant ctx check before Begin() as the fix — the correct fix is BeginTx, not a manual ctx.Err() guard"},
      {"id": "8.5", "text": "Each method accepts ctx context.Context as its first parameter and it flows through all DB operations"}
    ]
  },
  {
    "id": 9,
    "name": "context-values-abuse",
    "description": "Tests that the model does not abuse context values for function parameters",
    "prompt": "Write a Go service that processes orders. The service needs: a database connection, a logger, a user ID (from auth), a trace ID (from middleware), and the order details. Design the ProcessOrder function signature and show how to pass all these dependencies.",
    "trap": "Model might stuff the database connection, logger, or order details into context values instead of passing them as explicit parameters or struct fields",
    "assertions": [
      {"id": "9.1", "text": "Database connection is passed as a struct field or explicit parameter, NOT via context value"},
      {"id": "9.2", "text": "Logger is passed as a struct field or explicit parameter, NOT via context value"},
      {"id": "9.3", "text": "Order details are passed as an explicit function parameter, NOT via context value"},
      {"id": "9.4", "text": "User ID and/or trace ID are stored in context values (these are request-scoped metadata)"},
      {"id": "9.5", "text": "Distinguishes between infrastructure dependencies (explicit) and request-scoped metadata (context values)"}
    ]
  },
  {
    "id": 10,
    "name": "afterfunc-cleanup",
    "description": "Tests awareness of context.AfterFunc for cleanup callbacks (Go 1.21+)",
    "prompt": "Write a Go function that opens a temporary file for processing and must clean it up when the request context is cancelled. The file should be deleted asynchronously when the context is done, without blocking the main processing flow. The main function should continue processing immediately after registering the cleanup. Target Go 1.21+.",
    "trap": "Model might use a goroutine with <-ctx.Done() manually or use defer (which doesn't handle context cancellation). Skill teaches context.AfterFunc for non-blocking cleanup callbacks",
    "assertions": [
      {"id": "10.1", "text": "Uses context.AfterFunc to register the cleanup callback"},
      {"id": "10.2", "text": "Does NOT block the main flow waiting for context cancellation (no <-ctx.Done() in the main goroutine for cleanup purposes)"},
      {"id": "10.3", "text": "The cleanup function removes the temporary file"},
      {"id": "10.4", "text": "Captures the stop function returned by AfterFunc for potential cancellation of the callback"},
      {"id": "10.5", "text": "Also includes a defer-based cleanup as a safety net (AfterFunc + defer for belt-and-suspenders)"}
    ]
  }
]

Requirements and compatibility

Written for: Claude Code, OpenAI Codex

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Declares allowed tools, Claude Code frontmatter

The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
samber
Publisher
Samuel Berthe (Community)
Distributed by
Samuel Berthe
Source
samber/cc-skills-golang · skills/golang-context · commit bb1d43d
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-context.zip