Go

Go Code Review with LGTM

AI code review for Go: LGTM catches ignored errors, shadowed variables in if-bodies, goroutine leaks, missing context.Context plumbing, unsafe nil-pointer derefs, and missing defer cleanup.

How LGTM reviews Go PRs

Tree-sitter parses every .go file in your repo, extracting funcs, methods, interfaces, structs, and the imports that connect them. Go's explicit import paths and exported-by-capitalisation rule make symbol extraction extremely clean — symbol resolution is more deterministic than in TypeScript or Python.

Per-PR: the 5-15 highest-PageRank-ranked symbols related to the diff get attached to agent prompts. The Bugs agent for Go focuses on the specific failure modes Go's design philosophy expects you to handle explicitly: error returns, context cancellation, goroutine lifecycle, channel closing.

Go's standard formatting (gofmt) means style debates are mostly settled. LGTM's Readability agent focuses on naming, function length, and the comment-driven godoc conventions that signal a well-maintained package.

Common Go bugs LGTM catches

Ignored errors. `data, _ := io.ReadAll(r)` — the `_` discards a real error. The Bugs agent flags every `_` assignment of an error return value, with a high-confidence finding because Go's design says errors are values to be checked.

Shadowed variables in if bodies. `if err := fn(); err != nil { ... }` is fine. `err := fn()` followed by `if err != nil { err := otherFn(); ... }` shadows the outer err — and the recovery logic silently uses wrong-scoped err. Flagged.

Goroutine leaks. `go fn()` where fn never returns and has no way to be signaled to stop. The Performance agent surfaces these especially in long-lived processes (servers, queue workers).

Missing context.Context plumbing. A function that does network I/O but takes no `ctx context.Context` parameter — uncancellable, untimeoutable. Flagged as anti-pattern in repos that use ctx elsewhere.

Nil-pointer derefs on interface receivers. `var p *Plugin; p.Init()` panics if Init isn't pointer-safe. The agent reads receiver definitions and flags interface-receiver calls without nil checks.

Missing defer for cleanup. `f, _ := os.Open(...)` without `defer f.Close()`. Filesystem leak. Caught by the Bugs agent.

Tree-sitter coverage for Go

Go's tree-sitter grammar is excellent — Go's syntax is small and unambiguous, parser produces clean trees.

Generics (Go 1.18+) parse correctly with type parameter declarations and constraints. Method sets with embedded interfaces resolve properly.

Go modules (go.mod) are read at index time to understand cross-package dependencies. Module path → import path → file path resolution lets the indexer build cross-package symbol graphs even in deeply nested monorepos.

cgo files (.go with // #cgo lines) parse the Go part; the embedded C is treated as opaque. C bugs in cgo aren't reviewed (that's a C agent's job — see the C++ glossary page).

Setup notes for Go projects

Install the LGTM GitHub App on the repo. Index time for typical Go services: 10-30 seconds. Large monorepos with many modules: 1-3 minutes.

go.mod and go.sum are detected automatically. Replace directives (for vendored or local modules) are honoured during import resolution.

Generated Go code (protoc output, ent-generated, mockgen) is auto-excluded by common heuristics (DO NOT EDIT headers, /generated/ paths). Custom-generated paths can be added to .lgtmignore.

Test files (_test.go) get reviewed — but with a different bar. Test code can have looser error handling (t.Fatal is fine), shorter naming, less docstring rigour. The agents adjust their bar by file naming conventions.

Example bugs LGTM catches

Shadowed err variable
// ❌ Bug: inner err shadows outer; outer never sees real error
func update(id int) (err error) {
    user, err := fetch(id)
    if err != nil {
        // recovery attempt
        u, err := fallback(id)    // <-- shadow!
        if err != nil {
            return err            // returns INNER err
        }
        user = u
    }
    return saveUser(user)
}

// LGTM finding:
// "Inner 'err' shadows outer 'err' declared in named return.
//  Use 'u, err = fallback(id)' (no shadowing) so the named
//  return reflects the actual error path."
Goroutine leak with no cancellation
// ❌ Bug: goroutine runs forever, no way to stop
func startWorker(jobs chan Job) {
    go func() {
        for {
            job := <-jobs
            process(job)
        }
    }()
}

// LGTM finding:
// "Goroutine has no exit path. Accept context.Context,
//  select on ctx.Done() in the loop:
//    for { select { case <-ctx.Done(): return; case job := <-jobs: ... } }"

See LGTM's Go-aware review pipeline

Go module / generics / context-aware · BYOK

Go to the product page

Go review FAQs

Does LGTM run go vet or staticcheck?

No — LGTM is static analysis via tree-sitter + LLM reasoning, not execution of go vet. Run go vet + staticcheck in your CI; LGTM complements them by catching judgment-call patterns those tools miss (subtle shadowing in long functions, missing context plumbing, goroutine lifecycle).

What about cgo and assembly?

cgo Go-side parses cleanly; the C portion is opaque to the Go review. .s assembly files are not reviewed. If you're doing heavy cgo, those files won't get AI review — but they typically need a different review process anyway.

Generics support?

Yes — Go 1.18+ generics parse correctly. The agents reason about type constraints when surfacing findings about generic functions.

Multi-module monorepo support?

Yes. Each module's go.mod is detected. Cross-module references via replace directives or workspace mode (go.work) resolve to the local files when present.

Cost per Go review?

$0.05-$0.12 for a 300-line PR on GPT-4o via BYOK. Go's compact code style means typical diffs are smaller than Python or TypeScript equivalents — same fix often takes fewer lines, lowering token cost.

Related across LGTM

Other languages