# Go Error Review

> Review Go code for error handling correctness, nil safety, and failure-path integrity including ignored errors, missing wrapping, panic misuse, SQL/HTTP resource lifecycle, and transaction patterns. Trigger when code contains error returns, panic calls, sql.Rows, transactions, HTTP client/server code, or nil-sensitive pointer operations. Use for error-handling and correctness-focused review.

- Skill: `johnqtcg/go-error-review` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add johnqtcg/go-error-review`
- Raw SKILL.md: https://api.skillmd.com/api/skills/johnqtcg/go-error-review/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: johnqtcg (https://skillmd.com/u/johnqtcg)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/johnqtcg/go-error-review

---


# 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 `[]*T` pointer 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

1. **Define scope** — files/diff under review. Apply Generated Code Exclusion Gate.
2. **Gather evidence** — read changed files, identify error-handling patterns: `if err != nil`, `_ =`, `panic(`, `sql.Rows`, `tx.`, `resp.Body`, `[]*T`.
3. **Load references** — always load `go-error-and-quality.md` (error sections); load `go-api-http-checklist.md` when net/http code present; load `go-database-patterns.md` when database code present.
4. **Evaluate ALL 12 checklist items** — for each function call, ask "what happens on failure?"
5. **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
1. Identify target files (from dispatch prompt, or write raw snippet to `$TMPDIR/review_snippet.go`)
2. Run grep for ALL 12 checklist items against target files
3. **HIT** → run semantic analysis to confirm or reject (true positive vs false positive)
4. **MISS** → auto-mark NOT FOUND, skip semantic analysis for that item
5. For compound patterns (item 12): run both grep patterns, apply AND logic
6. 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.Y
- `Grep pre-scan`: X/12 items hit, Z confirmed as findings
- `go test`: PASS | FAIL | Not run (reason + command)
- `Excluded (generated)`: list or None
- `References 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

