Propagate context.Context Through Go Request Paths

Catches Go code that drops context.Context: Background() in request paths, calls without QueryContext or NewRequestWithContext, and missing deadlines.

What it flags

Flaggedbilling/invoices.go
func (s *InvoiceService) Get(ctx context.Context, id string) (*Invoice, error) {
	inv := Invoice{ID: id}
	err := s.db.QueryRow(selectInvoice, id).Scan(&inv.CustomerID, &inv.TotalCents)
	if err != nil {
		return nil, fmt.Errorf("load invoice %s: %w", id, err)
	}

	endpoint := fmt.Sprintf("%s/customers/%d", s.billingURL, inv.CustomerID)
	resp, err := http.Get(endpoint) // ignores ctx: a stalled billing API hangs the caller
	if err != nil {
		return nil, fmt.Errorf("fetch customer %d: %w", inv.CustomerID, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("fetch customer %d: status %d", inv.CustomerID, resp.StatusCode)
	}
	if err := json.NewDecoder(resp.Body).Decode(&inv.Customer); err != nil {
		return nil, fmt.Errorf("decode customer %d: %w", inv.CustomerID, err)
	}
	return &inv, nil
}
Passesbilling/invoices.go
func (s *InvoiceService) Get(ctx context.Context, id string) (*Invoice, error) {
	inv := Invoice{ID: id}
	err := s.db.QueryRowContext(ctx, selectInvoice, id).Scan(&inv.CustomerID, &inv.TotalCents)
	if err != nil {
		return nil, fmt.Errorf("load invoice %s: %w", id, err)
	}

	endpoint := fmt.Sprintf("%s/customers/%d", s.billingURL, inv.CustomerID)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, fmt.Errorf("build customer request: %w", err)
	}
	resp, err := s.http.Do(req) // *http.Client with Timeout set
	if err != nil {
		return nil, fmt.Errorf("fetch customer %d: %w", inv.CustomerID, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("fetch customer %d: status %d", inv.CustomerID, resp.StatusCode)
	}
	if err := json.NewDecoder(resp.Body).Decode(&inv.Customer); err != nil {
		return nil, fmt.Errorf("decode customer %d: %w", inv.CustomerID, err)
	}
	return &inv, nil
}

Why it matters

When a request path drops its context, a client that disconnects or a caller whose deadline expires does not stop the database query or outbound call underneath it. Under a slow dependency those orphaned calls pile up, hold pool connections, and spread one slow service into an outage. http.DefaultClient has no timeout, so a stalled upstream can hang a request indefinitely. The code works in tests, where every dependency answers quickly, so the gap only shows under production latency.

Agent instructions

cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.

AGENTS.md

Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.

## Propagate context.Context Through Go Request Paths

Applies to `**/*.go`.

Review Go request paths for a context.Context that is dropped, replaced, or never given a deadline.

Flag changes that:
- Call `context.Background()` or `context.TODO()` inside an HTTP handler, gRPC method, or any function that already receives a `ctx` or `*http.Request`, instead of passing the incoming context or `r.Context()`.
- Accept a `ctx` parameter and then call downstream I/O without it.
- Use a call that cannot be canceled when a context variant exists: `db.Query`, `db.Exec`, or `db.QueryRow` instead of `QueryContext`, `ExecContext`, or `QueryRowContext`; `http.Get` or `http.NewRequest` instead of `http.NewRequestWithContext`; `exec.Command` instead of `exec.CommandContext`.
- Make outbound calls through `http.DefaultClient` or an `http.Client` with no `Timeout` when no caller deadline bounds the request.
- Retry or poll in a loop with `time.Sleep` that never checks `ctx.Done()` or `ctx.Err()`, so cancellation cannot stop it.
- Start background work from a handler with the request context, which is canceled when the handler returns, instead of `context.WithoutCancel` plus its own timeout.
- Store a `context.Context` in a struct field instead of passing it as the first parameter.

Do not flag:
- `context.Background()` in `main`, `init`, tests, and at the top of workers or CLI commands that create the root context.
- Deliberate detachment with `context.WithoutCancel` or a new root context for work that must outlive the request, when it sets its own timeout.
- Calls to libraries that expose no context-aware API.

Checks files matching **/*.go.

More Go agents

See all Go code review standards

Run this agent on every pull request

cubic reviews every pull request against the agents you turn on and comments on the lines that break them.

Catch this in your code