Go API Design Review Skill
Handler, error, and context conventions for automated review - a cookbook-style Agent Skill for auditing Go 1.26 HTTP and gRPC handlers without unsafe auto-fixes.
What This Skill Does
Produces a structured review checklist for handlers: context propagation, error mapping, status codes, request validation, middleware ordering, and response shape - each finding tied to file and line references.
When to Invoke
- Before merging handler or middleware changes
- When an agent drafts a new REST package
- During design review for public API surfaces
- When standardizing error responses across services
- After incidents caused by missing timeouts or wrong status codes
Inputs
| Input | Why |
|---|---|
go.mod | Module path, Go version directive |
| Affected packages | Scope go test and lint |
| Router choice | stdlib ServeMux vs chi/gin/echo per ADR |
| Error-handling ADR | Typed errors vs sentinel pattern |
| Auth/logging ADR | Middleware expectations |
| OpenAPI or route table | Expected methods and paths |
Outputs
- Checklist grouped by: context, errors, validation, middleware, responses
- Severity: blocker / warning / nit with
file:linereferences - Suggested fix snippets (human applies - no auto-merge)
- Verification commands:
go test ./pkg/...,golangci-lint run ./pkg/...
Guardrails
- Propagate
r.Context()to all outbound I/O - DB, HTTP client, gRPC. - Map errors in one layer - handlers or a dedicated
apierrpackage, not both. - Never return 500 for client validation failures - use 400/422 per team policy.
- Never log and return internal error strings to clients - use opaque IDs.
- Do not disable linters (
errcheck,contextcheck) without ADR exception. - Stdlib ServeMux on Go 1.22+ - verify method-aware patterns before suggesting chi migration.
Recipe
Quick-reference recipe card - copy-paste ready.
// Review targets (stdlib handler)
func getUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // must flow to store.GetUser(ctx, id)
id := r.PathValue("id")
if id == "" {
writeError(w, apierr.BadRequest("missing id"))
return
}
u, err := store.GetUser(ctx, id)
if err != nil {
writeError(w, mapStoreErr(err))
return
}
writeJSON(w, http.StatusOK, u)
}# Verification after review fixes
go test ./internal/api/...
golangci-lint run ./internal/api/...
go vet ./internal/api/...When to reach for this skill:
- Tech lead needs a consistent handler review before Friday deploy
- Agent drafted handlers without reading team error ADR
- New engineer's first HTTP PR needs guardrailed feedback
Working Example
Step 0 - Collect inputs
go list ./internal/api/...
grep -r "ServeHTTP\|HandleFunc\|chi\|gin\." internal/api/| Signal | Action |
|---|---|
Missing r.Context() on db.Query | Blocker |
http.Error(w, err.Error(), 500) on validation | Blocker |
Handler calls log.Printf and returns err text | Blocker |
Mixed fmt.Errorf and sentinel in same package | Warning |
Step 1 - Context audit
Check every outbound call receives ctx derived from r.Context():
// BAD: context.Background() in handler
user, err := repo.Find(context.Background(), id)
// GOOD: request-scoped context
user, err := repo.Find(r.Context(), id)- Server timeouts (
ReadHeaderTimeout,WriteTimeout) cancelr.Context()- downstream must respect it - See Middleware Chains and Request Context
Step 2 - Error mapping audit
func mapStoreErr(err error) apierr.Response {
if errors.Is(err, store.ErrNotFound) {
return apierr.NotFound("user")
}
if errors.Is(err, store.ErrInvalid) {
return apierr.BadRequest("invalid user id")
}
return apierr.Internal("user lookup failed") // opaque to client
}- One mapping function per bounded context
- gRPC: map to
codes.InvalidArgument,codes.NotFound- see grpc-Go patterns in sibling sections
Step 3 - Status and response shape
| Condition | Status | Body |
|---|---|---|
| Validation failure | 400 or 422 | { "error": "...", "code": "..." } |
| Auth failure | 401 / 403 | No internal details |
| Success | 200 / 201 | Resource or list per OpenAPI |
| Unknown server fault | 500 | Opaque ID + server-side log |
Step 4 - Verify
go test ./internal/api/... -count=1
golangci-lint run ./internal/api/...Deep Dive
Middleware ordering affects review findings.
Logging and request ID middleware should wrap auth, which wraps business handlers.
Panic recovery belongs outermost.
Agents often insert auth after handlers in drafts - flag as blocker.
Validation belongs at the boundary: path params, query, JSON body.
Use encoding/json with strict decoding (DisallowUnknownFields) when ADR requires.
For chi/gin, confirm param extraction matches registered route patterns.
Idempotency and methods: GET and HEAD must not mutate state.
POST create vs PUT upsert policy should match OpenAPI.
Go 1.22+ ServeMux method patterns (GET /users/{id}) prevent accidental method-wide handlers.
Gotchas
http.Errorwitherr.Error()leaks implementation details and often uses wrong status.- Nested
context.WithTimeoutwithoutdefer cancel()- linters catch some; review manually. - Writing JSON after
WriteHeader- status locked on first write; use helper that sets headers once. - Default
http.Clienthas no timeout - flag outbound calls from handlers usinghttp.DefaultClient. - chi URL params vs PathValue - different APIs; skill must know router from inputs.
Alternatives
| Approach | When |
|---|---|
| Human design review only | Small teams, low traffic |
| OPA / policy-as-code | Org-wide HTTP standards |
| OpenAPI linter in CI | Contract-first APIs |
| This skill | Assisted PR review with team ADR alignment |
FAQs
Should the skill auto-apply fixes?
No. Output checklist and suggested diffs only. Human merges after go test and lint pass.
Does this skill cover gRPC?
Yes for context, status mapping (codes.*), and error wrapping. HTTP-specific sections skip gRPC-only packages - collect router/proto inputs first.
What if team uses gin instead of stdlib?
Collect framework in inputs. Review gin.HandlerFunc for c.Request.Context() propagation and centralized c.JSON error helper.
Related
- Agent Skills Basics - SKILL.md structure
- HTTP Servers, Handlers & ServeMux Patterns - routing reference
- Middleware Chains and Request Context - context flow
- Input Validation and Safe HTML/JSON Handling - boundary validation
- httptest for Handler Integration Tests - test patterns
- Agent Skills Best Practices - team-wide skill rules
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).