go-backend-pr-review
Skill1 starsChanged 2 months agoMITView on GitHub
Review a Go backend PR the way Tejas reviews — correctness first (concurrency safety, error handling, context propagation), then optional-dependency degradation, queue/message lifecycle, then reuse and dead-code hygiene. Terse blocking/non-blocking split, same register as nestjs-backend-pr-review. Use when reviewing a PR, diff, or branch in a Go service (SQS/queue consumers, HTTP services, CLIs), or when asked to "review this Go PR like I would".
- Reads credentials
In this repository73 other files it gives its agents.
- AGENTS.mdAGENTS.md
- bigquery-analyticsSkill
- cache-invalidationSkill
- mongo-aggregationSkill
- mongo-modelingSkill
- postgres-aggregationSkill
- postgres-indexingSkill
- redis-coordinationSkill
- redis-patternsSkill
- review-querySkill
- code-migrationSkill
- cut-releaseSkill
- data-migrationSkill
- deploy-serviceSkill
- hotfixSkill
- rollbackSkill
- design-endpointSkill
- design-eventSkill
- design-schemaSkill
- evolve-contractSkill
- scaffold-nestjs-moduleSkill
- scaffold-projectSkill
- incident-responseSkill
- investigate-bugSkill
- on-callSkill
- postmortemSkill
- audit-skillsSkill
- distill-review-styleSkill
- write-skillSkill
- debug-with-tracesSkill
- define-alertsSkill
- instrument-serviceSkill
- evaluate-dependencySkill
- grillSkill
- handoffSkill
- orientSkill
- phased-deliverySkill
- spikeSkill
- to-questionnaireSkill
- write-adrSkill
- write-prdSkill
- devops-pr-reviewSkill
- nestjs-backend-pr-reviewSkill
- python-backend-pr-reviewSkill
- react-frontend-pr-reviewSkill
- bisectSkill
- break-couplingSkill
- dead-code-sweepSkill
- deslopifySkill
- extract-moduleSkill
- rebase-cleanSkill
- resolve-merge-conflictsSkill
- split-commitSkill
- tddSkill
- wait-whatSkill
- wide-renameSkill
- bullmqSkill
- kafkaSkill
- sqs-consumerSkill
- audit-dependenciesSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 4 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 18
Not a generic Go style guide.
- Very specific: line 183
Inline: default one or two sentences.
- Distinctive: line 214
Read the PR description. One-liner or overclaim → finding.
- Distinctive: line 225
Check whether a sibling service needs the identical change.
- Distinctive: line 226
Write ≤4 "before merge" items, split blocking / non-blocking, rest inline.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Go backend PR review — house style
Same reviewer, same voice as pr-review/nestjs-backend-pr-review.
The difference is ordering: in Go, correctness outranks reuse. The language makes
concurrency, error handling and lifecycle easy to get subtly wrong, and those bugs
ship silently — so they come first.
Grounded in the event-service material from the Nest corpus (SQS consumer, OTEL,
BigQuery) and the conventions in the Go repos (golangci-lint, gosec +
govulncheck, table-driven tests beside the source, go.mod as the version
source, multi-stage distroless-ish Docker builds).
Not a generic Go style guide. Read §5 of the Nest skill for the register; this file is the Go-specific checklist and the priority order.
1. The one-line summary of the style
Trace the failure before you flag it; degrade, don't crash; classify every error. One or two sentences per comment, lowercase, addressed to a named person, phrased as a question or a directive. Long comments only when the mechanism isn't obvious from the line — then quote the caller, the line number, and what happens on the next cycle.
2. What to flag — the recurring standards
Ranked by how much they matter in Go review, not by volume.
2.1 Error handling — terminal vs retryable — the first thing to check
- Every new
errorreturn path must be classified: is it terminal (drop / DLQ) or retryable (redeliver)? An error that is neither deleted nor DLQ'd redelivers forever → blocking. - Errors swallowed with
_ =or logged-and-continued where the caller needed to know → blocking if it corrupts state, else 🟠. fmt.Errorfwithout%wwhen the caller doeserrors.Is/errors.Asdownstream → wrap it. Conversely, don't%w-wrap when you're deliberately hiding an internal error from a boundary.- Sentinel errors (
var ErrNotFound = errors.New(...)) compared with==instead oferrors.Is→ breaks the moment someone wraps. panicin library / request-path code for something that should be an error return → blocking.panicis for programmer bugs, not bad input.
2.2 Concurrency safety
- Goroutine started with no lifecycle owner — nothing cancels it, nothing waits
on it → leak. Ask for a
context.Context+sync.WaitGroupor an errgroup. - Loop variable captured by a goroutine / closure inside
for(pre-1.22 semantics, or any version if you're not certain) → shadow it:item := item. - Shared map / slice / counter written from multiple goroutines without a mutex
or channel → data race. Ask if
go test -racewas run. context.Contextnot threaded through — a new blocking call (DB, HTTP, queue) that ignores the caller's ctx can't be cancelled on shutdown or timeout.context.Background()/context.TODO()deep in a call tree where a real ctx was available → thread the real one.- Unbuffered channel send with no guaranteed receiver → deadlock on the unhappy path. Trace both sides.
deferinside a loop (file handles, unlocks, spans) → they stack until the function returns. Move to an inner func ordeferexplicitly per iteration.
2.3 Optional dependencies must degrade, never crash
- Telemetry / cache / read-replica init failing must not take the service down.
log.Fatalfon OTEL / metrics / tracing init → blocking; fall back to a no-op provider. "Observability optional honi chahiye, hard startup dependency nahi." - Read-replica connect failure must fall back to primary, not block boot.
- Reconnect that gives up permanently after N retries → a blip after a deploy leaves every call throwing until the pod restarts. Reconnect infinite with capped backoff; bound only the initial connect.
- If the author genuinely wants loud-fail, that's a valid stance — but then stop calling the dependency "optional" in the code and docs. Pick one.
2.4 Queue / message lifecycle (SQS and similar consumers)
- Never delete / ack a message before the downstream write succeeds. Trace the order of operations.
- Confirm the deployed queue actually has the redrive policy / DLQ the code assumes. If the code relies on max-receive-count, say which value.
- Partial-batch failures: does one bad record fail the whole batch, or is it handled per-record? Match it to the queue's batch-item-failure support.
- Visibility timeout vs handler runtime — a handler that can run longer than the timeout causes duplicate processing. Ask.
2.5 Resource lifecycle
rows.Close(),resp.Body.Close(), file handles — every acquire needs a paired release on every path, including early error returns.http.Client/ DB pool / gRPC conn created per-request instead of once → fd exhaustion and no connection reuse.- Background tickers /
time.Afterin a loop →time.Afterleaks a timer per iteration until it fires; usetime.NewTimerandStop(). - No graceful shutdown —
SIGTERMshould drain in-flight work, close pools, flush telemetry. Checksignal.NotifyContext+ serverShutdown(ctx).
2.6 Reuse over new abstraction (still applies, just not #1)
- New interface / wrapper / struct introduced when an existing type, repo method or helper already covers it → "directly call the repo", "why are we creating interfaces again". Go especially: don't define an interface on the producer side — let the consumer declare the small interface it needs.
- Instruments / recorders / wrappers registered but never called → give the grep
that proves it: "
grep -rn 'RecordServiceCall\|ServiceWrapper' pkg/→ koi caller nahi milega". - Scaffolding for tech the stack doesn't run (kafka recorders when it's SQS).
- Same change needed in a sibling service → say so, or ask if it should be a shared internal package.
2.7 Config, env, dead environments
- Infra connection details in committed config files instead of env.
- Required env keys nothing reads → "i don't think this is used, please check".
- Config blocks for environments that don't exist → "remove this as we dont have a dev env for this service".
- Committed
.envwith real-looking values → flag secrets; dev placeholders get a one-line "confirm these are throwaway".
2.8 Naming & API
- Function names that stopped describing the function after a refactor →
"
findOrBackfillUserdoesnt backfill anymore, can we rename". - Exported identifiers that don't need to be — Go has no "internal by convention", so an exported symbol is API. Lowercase it if nothing external uses it.
- Package names too generic to own a namespace (
utils,common,helpers) → name for what's in it. - Wire/DB values (table names, enum strings, event names, JSON tags) that don't match the source of truth.
2.9 Tests
- New handler / new exported func with no test → ask for a table-driven test
(
tests := []struct{ name, input, want … }{…}→for _, tt := range tests { t.Run(tt.name, …) }), and be specific about what it should assert. - Test lives in a separate
tests/tree instead of<pkg>_test.gobeside the source → move it. t.Fatalfvst.Errorfmisuse —Fatalfor setup that makes the rest meaningless,Errorfor assertions so all failures surface in one run.- Concurrency-touching change with no
-racein the test command → ask. - Happy-path-only coverage when the PR's goal spans several call paths → enumerate the missing cases in a table.
2.10 Efficiency
- Per-item loop doing N round-trips when the API / driver takes a batch → chunk.
- Unbounded
errgroup/sync.WaitGroupfan-out over N items → bound with a semaphore. - Slice built with
appendin a hot loop with a known final size →make([]T, 0, n). - String concatenation with
+in a loop →strings.Builder. - Usually 🟡 "not a merge blocker, worth doing in the same pass" unless it's a genuinely hot path — then say so with the call volume.
3. What to let slide
- Style / formatting
gofmtandgolangci-lintalready own. err != nil { return err }verbosity — it's the language, not a finding.- Latent bugs with no in-repo caller today →
⚪ Minor, "latent", "not a blocker". - Defensible duplication on a cold path — say so explicitly so the fix targets the hot line and not this one.
- Micro-optimizations with no measured impact on a cold path.
- Things you suspected and then verified are fine — say you checked them.
4. How to structure the review
Same as the Nest skill §4. Recap:
- Review body: one line of specific praise → numbered "before merge" list (2–4 items) → explicit Blocking / Non-blocking split → "details inline".
CHANGES_REQUESTEDfor correctness / contract / forever-redelivery /Fatalfon optional deps.COMMENTfor everything else and all follow-up rounds.- Severity markers only on multi-finding reviews:
| Marker | Means (Go) |
| 🔴 Blocking | data race, goroutine leak on the request path, forever-redelivery, Fatalf on an optional dep, message deleted before write, panic on bad input |
| 🟠 | real bug, one-line fix — unwrapped error the caller inspects, missing Close() on the error path |
| 🟡 | efficiency / redundancy — unbatched loop, unbounded fan-out |
| ⚪ | latent, no caller today |
| 🟢 | checked and fine — noted so it isn't re-raised |
- Inline: default one or two sentences. When you assert a bug, show the mechanism — quote the caller, the line, and what happens on the next receive/tick/deploy. When you claim something is unused, paste the grep.
5. Phrasing — same register as the Nest skill
Lowercase starts, dropped apostrophes ("doesnt", "cant"), no sign-off, @name
opener, Hinglish freely with Indian teammates for the longer explanations
(technical nouns in English, connective tissue in Hinglish). ?? marks real doubt,
not volume. Hedge when unsure — "i dont think this ctx is cancelled on shutdown,
please check once" — rather than asserting. Offer to push the fix when it's bigger
than the comment. Give the decision, not a menu.
6. Anti-patterns for the reviewing agent
- ❌ Flagging
err != nilverbosity or asking for an interface. - ❌ Claiming a race / leak without tracing both goroutines. If you can't, phrase it
as "please run
go test -raceon this once". - ❌ Generic "add error handling" with no line anchor.
- ❌ A severity table on a 5-line diff.
- ❌ Restating what the code does.
- ❌ Blocking on style a linter owns.
7. Applying this to a diff — order of operations
- Read the PR description. One-liner or overclaim → finding.
- For every new
errorreturn: terminal or retryable? deleted or redelivered? - For every new goroutine: who cancels it, who waits on it?
- Grep the diff for
Fatalf,panic(,context.Background(),context.TODO(),go func(,deferinsidefor,time.After,.Close()(and check the error paths above each),_ =. - For every optional dep (telemetry, cache, replica): does init failure degrade or crash? does reconnect give up?
- For every new exported symbol: is it actually API? grep for a caller.
- For every changed wire value / response shape: is this endpoint legacy? (revert unconditionally if so — see Nest skill §2.11)
- Check whether a sibling service needs the identical change.
- Write ≤4 "before merge" items, split blocking / non-blocking, rest inline.
- State what you checked and cleared.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

