Skip to content

Go module

The Go module owns *.go files and go.mod.

Stage What runs
On edit: auto-fix goimports -w, then gofumpt -w (gofmt if gofumpt is missing). Generated files are skipped.
On edit: check go vet and golangci-lint run --fast-only on the edited file's package, reporting issues in that file. Type errors anywhere in the package are always reported, because the edit may have broken another file.
At Stop: gate go build ./..., go test -race -count=1 -covermode=atomic -coverprofile on touched packages, then the tests of every package that imports them, coverage checks, full golangci-lint, go mod tidy -diff, govulncheck ./...

Packages are grouped by their nearest go.mod, so multi-module repositories are gated module by module.

Dependent packages

A change can pass its own package's tests and still break a caller. After the touched packages pass, the gate runs the tests of every package in the module that imports them — directly or transitively, including through test imports. These run without -count=1, so Go's test cache skips any package whose dependencies didn't actually change; only the ones your change can affect re-run. A failure is reported as [dependents], with the caller's test file and line.

Coverage rules

Coverage is the bar that matters most, so it has four parts. All must hold.

Total. Statement coverage across touched packages must be at least go.coverage.total (default 90%).

Per-package floor. No touched package may fall below go.coverage.per_package_floor (default 80%), so one well-tested package can't hide an untested one.

Ratchet. With go.coverage.ratchet: true, no package may end below its coverage at SessionStart, even in repositories still under 90%. Each passing gate raises the baseline; it never moves down.

Patch coverage. Lines added or changed this session — git diff against the commit at SessionStart, plus untracked files — must be at least go.patch_coverage covered (default 90%). Lines without statements (comments, declarations) don't count. Failures list the uncovered changed lines grouped by function:

[patch-coverage] 66.7% of changed lines covered < 90.0% (8/12)
  internal/api/handler.go:94-97  decodeRequest
  fix: test the changed lines above; they are code you wrote this session

Exclusions. By default **/mocks/**, **/*_mock.go, **/*.pb.go, cmd/**/main.go, and any file with a // Code generated ... DO NOT EDIT. header are excluded. Patterns support ** and are relative to the module root.

On failure, the report lists the five functions with the most uncovered statements, with file:line, so Claude knows exactly where to write tests.

Lint config

If the repository has a golangci-lint config (.golangci.yml, .yaml, .toml or .json) it is used. Otherwise Guardrails uses its bundled config: the standard linters plus bodyclose, errorlint, gocritic, gosec, misspell, nolintlint, revive, unparam and friends, with gofumpt and goimports as formatters. go.lint.config points at a config elsewhere.

go.lint.on_edit controls the per-edit pass: fast (default, fast linters only), full, or off.

Toolchain

You need Go itself. Everything around it is provisioned automatically at a pinned, checksum-verified version when it isn't already on your PATH.

Tool Required If unavailable
go yes The gate fails with a tools check
golangci-lint (v2) yes The lint check fails, naming the fix
gofumpt no Falls back to gofmt
goimports no Imports aren't fixed on edit
govulncheck no The vulnerability check is skipped with a note

While a tool is still downloading in the background, per-edit checks that need it are skipped; the Stop gate waits for it.

Hardening

The gate checks coverage; guardrails harden checks that the tests behind your changes actually assert something — mutation testing of changed functions and short fuzz runs, on demand. See Hardening tests.