Go Error Review¶
Purpose¶
Audit Go code for error handling correctness, nil safety, and failure-path integrity. Core question for every function call: "What happens when it fails?"
This skill merges error handling + API request/response correctness + database operation correctness because they share one review lens: "does this code handle failure correctly?"
This skill does NOT cover: security vulnerabilities, concurrency safety, performance, code style, test quality, or business logic — those belong to sibling vertical skills.
When To Use¶
- Code contains error return values
- Code uses
panic/recover - Code involves
sql.Rows, transactions, connection pools - Code involves HTTP request/response body handling
- Code operates on
[]*Tpointer slices
When NOT To Use¶
- Security vulnerabilities →
go-security-review - Concurrency safety →
go-concurrency-review - Performance optimization →
go-performance-review - Code style →
go-quality-review - Business logic →
go-logic-review
Mandatory Gates¶
1) Execution Integrity Gate¶
Never claim tests ran unless they actually did. If not run: state reason + exact command.
2) Go Version Gate¶
Read go.mod. Key features: - errors.Is / errors.As (Go 1.13+) - errors.Join (Go 1.20+) - fmt.Errorf with multiple %w (Go 1.20+)
3) Anti-Example Suppression Gate¶
MUST quote specific code evidence. Category match alone insufficient.
Embedded anti-examples: - "Missing error handling on json.Marshal" — when marshaling known-safe struct with only primitive fields (string, int, bool, no interface fields). json.Marshal on such structs always returns nil error. - "Missing error wrapping" — when caller already wraps; adding another layer creates redundant context like "create user: insert user: insert row: ...". Cite the caller's wrapping code. - "Speculative nil dereference" — when caller is internal and always passes non-nil. Cite the caller code proving it never passes nil. - "Should use errors.Is instead of ==" — direct == against sentinel from the same package is acceptable. Cross-package comparison must use errors.Is. - "defer f.Close() ignoring error" — acceptable for read-only file opens. Flag only for write operations where Close flushes buffered data.
4) Generated Code Exclusion Gate¶
Exclude: *.pb.go, *_gen.go, mock_*.go. Note excluded files in Execution Status.
Workflow¶
- Define scope — files/diff under review. Apply Generated Code Exclusion Gate.
- Gather evidence — read changed files, identify error-handling patterns:
if err != nil,_ =,panic(,sql.Rows,tx.,resp.Body,[]*T. - Load references — always load
go-error-and-quality.md(error sections); loadgo-api-http-checklist.mdwhen net/http code present; loadgo-database-patterns.mdwhen database code present. - Evaluate ALL 12 checklist items — for each function call, ask "what happens on failure?"
- Apply suppression → format output.
Grep-Gated Execution Protocol¶
This skill uses mechanical grep pre-scanning to guarantee zero missed checklist items. The model's attention is reserved for semantic judgment on grep hits and semantic-only items.
Execution Order¶
- Identify target files (from dispatch prompt, or write raw snippet to
$TMPDIR/review_snippet.go) - Run grep for ALL 12 checklist items against target files
- HIT → run semantic analysis to confirm or reject (true positive vs false positive)
- MISS → auto-mark NOT FOUND, skip semantic analysis for that item
- For compound patterns (item 12): run both grep patterns, apply AND logic
- Report only FOUND items (grep-confirmed + semantic-confirmed)
Grep Audit Line¶
Include in Execution Status: Grep pre-scan: X/12 items hit, Z confirmed as findings
Compound Pattern Protocol¶
Some items require two grep patterns. Run both: - Item 12 (Log-and-return): TRIGGER when log\.\|slog\. HIT AND return.*err HIT in same file
Error Checklist (12 Items)¶
Error Handling (High)¶
| # | Item | Code Pattern Triggers | Grep Pattern |
|---|---|---|---|
| 1 | Ignored error | _ = or _ := on error-returning calls. Acceptable only for hash.Write, known-safe fmt.Fprintf to buffer | _\s*[:=]= |
| 2 | Missing error wrapping | return err without fmt.Errorf("context: %w", err) at abstraction boundary | return\s+(nil,\s*)?err\b |
| 3 | Panic misuse | panic() for recoverable errors. Acceptable only in init() or unrecoverable invariant violation | panic\( |
| 4 | Missing errors.Is/As | Direct == on error for cross-package sentinel; type switch instead of errors.As | err\s*[!=]=\s*\|[!=]=\s*err |
| 5 | Pointer slice nil guard | []*T elements accessed without nil check before field/method access | \[\]\*\w |
API/HTTP Correctness (High)¶
| # | Item | Code Pattern Triggers | Grep Pattern |
|---|---|---|---|
| 6 | Unbounded server body | Missing http.MaxBytesReader / io.LimitReader on body decode. Do NOT require r.Body.Close() — framework handles it | r\.Body\|Request\.Body\|ReadAll |
| 7 | Client response body leak | resp.Body not closed on ALL paths including error path — prevents connection reuse | resp\.Body\|Response\.Body |
| 8 | HTTP status code mismatch | 200 for creation (should be 201), 500 for not-found (should be 404) | WriteHeader\|StatusCode\|http\.Status |
Database Correctness (High)¶
| # | Item | Code Pattern Triggers | Grep Pattern |
|---|---|---|---|
| 9 | Unclosed sql.Rows | Missing defer rows.Close() AFTER error check; missing rows.Err() after iteration loop | \.Query[^R]\|\.QueryRow\|sql\.Rows |
| 10 | Wrong transaction rollback pattern | Missing defer tx.Rollback() + Commit override pattern | \.Begin\(\|tx\. |
| 11 | sql.ErrNoRows mishandled | Treating as server error (500) instead of domain "not found" (404) | ErrNoRows |
| 12 | Log-and-return double reporting | Logging error AND returning it — causes duplicate log entries upstream | log\.\|slog\. (compound: ALSO check return.*err in same file) |
Severity Rubric¶
High — Resource leak, crash, silent failure, data inconsistency.
Medium — Suboptimal error handling that makes debugging harder but no immediate failure.
Evidence Rules¶
- For each finding: explain what happens on the failure path
- For resource leaks: show the code path where Close/Rollback is missed
- For body leaks: show the error-path branch that skips Close
- Merge rule: same issue at ≥3 locations → one finding with location list
Output Format¶
Findings¶
[High|Medium] Short Title¶
- ID: ERR-NNN
- Location:
path:line - Impact: What happens when this code path fails
- Evidence: The missing error check / resource close / wrapping
- Recommendation: Specific fix
- Action:
must-fix|follow-up
Suppressed Items¶
[Suppressed] Short Title¶
- Reason: Anti-example matched + evidence cited
Execution Status¶
Go version: X.YGrep pre-scan: X/12 items hit, Z confirmed as findingsgo test: PASS | FAIL | Not run (reason + command)Excluded (generated): list or NoneReferences loaded: list
Summary¶
1-2 lines with finding count.
Example Output¶
### Findings
#### [High] Response Body Leak in HTTP Client
- **ID:** ERR-001
- **Location:** `internal/client/api.go:45`
- **Impact:** Connection pool exhaustion — resp.Body not closed on error path
- **Evidence:** `resp, err := client.Do(req)` at L42; if `resp.StatusCode != 200` at L44, function returns error at L46 without closing resp.Body. Body only closed on happy path at L52.
- **Recommendation:** Move defer immediately after nil-error check:
```go
resp, err := client.Do(req)
if err != nil { return err }
defer resp.Body.Close()
```
- **Action:** must-fix
#### [High] Missing rows.Err() After Iteration
- **ID:** ERR-002
- **Location:** `internal/repo/order.go:78`
- **Impact:** Silent data truncation — if iteration breaks due to network error, partial results returned without error
- **Evidence:** `for rows.Next() { ... }` loop at L73-80 exits without checking `rows.Err()`
- **Recommendation:** Add after loop: `if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterating orders: %w", err) }`
- **Action:** must-fix
### Suppressed Items
#### [Suppressed] json.Marshal Error Ignored
- **Reason:** `json.Marshal(config)` at config.go:30 — `config` is `AppConfig` struct with only primitive fields. Anti-example: "known-safe struct with no interface fields"
### Execution Status
- Go version: 1.21
- Grep pre-scan: 5/12 items hit, 2 confirmed as findings
- go test: PASS
- Excluded (generated): None
- References loaded: go-error-and-quality.md, go-api-http-checklist.md, go-database-patterns.md
### Summary
2 High findings (response body leak, missing rows.Err). No Medium findings.
No-Finding Case¶
If no issues found: state No error handling findings identified. Still output Execution Status.
Load References Selectively¶
| Reference | Load When |
|---|---|
references/go-error-and-quality.md | Always (error handling sections) |
references/go-api-http-checklist.md | Code involves net/http, handlers, gin/echo/chi, gRPC |
references/go-database-patterns.md | Code involves database/sql, pgx, sqlx, gorm, ent |
references/go-review-anti-examples.md | Always |
Review Discipline¶
- Error handling, nil safety, failure-path integrity only — not security, concurrency, performance, style, tests, or logic
- For every function call: "what happens when it fails?"
- Execute ALL 12 checklist items without skipping
- Server handler
r.Body: do NOT require manual Close (framework handles it) - Client
resp.Body: MUST be closed on all paths