# Go - Lint and Vulnerability Tooling Most lint suppression failures are placement or tool-behavior questions, not code questions. Lint directives work only in exact positions, `gofmt` undoes hand-padded comment spacing, and `unparam` hides a call-site threshold. `govulncheck` blocks CI only on reachable vulnerabilities, but its output is only as good as the vulnerability database. ## Key points - Put `//nolint:<linter>` on the line directly above the function declaration. Same-line comments and doc-comment position do not suppress function-level checks. In golangci-lint v2, `nolintlint` then flags the directive as unused while the linter still fires. - One allowlist entry in `.golangci.yml` covers deliberately-returned interfaces, so you avoid per-function annotations. - A standalone `//nolint` covers only the value's own line and the line above it in a multi-line call. Later lines still get reported. - Do not pad directive comments with extra spaces to align them. `gofmt` re-aligns comments within a struct field group to its own rules. The manual padding is undone and the format check fails. - `gochecknoglobals` fires on package-level `var` in test files. Write test helpers as functions, not vars, when the linter runs without a `testpackage` exception. - Reduce cyclomatic complexity (`cyclop`) by extracting validation into narrow helpers instead of one large validate function. Each branch then becomes testable on its own. - Run `golangci-lint` and its `--fix` from each module directory in a multi-`go.mod` repository. Root-module runs miss sub-module violations. Run `goimports` after `--fix`, because some fixes leave a stale import behind. - `unparam` reports a function only when it has at least 4 call sites. The fourth call site flips CI lint from green to failing without any wrong code. Count call sites before you add one. - Run a custom analyzer with `go vet -vettool=/path/to/analyzer ./...`. You get standard package-pattern handling and diagnostics for free. - Treat unit suffixes after a digit as initialisms in identifiers: `Ephemeral1H`, `Ephemeral5M`, `Buffer64K`, never `1h` or `5m`. - Analyzer edge case: a `*` width or precision operand advances the format index but not the operand counter. `%*v` then misattributes the width operand. - `govulncheck` works at the call-graph level. It flags only vulnerabilities in code paths the binary actually reaches. This produces far fewer false positives than module-level scanning such as Dependabot's. - Rebuild `govulncheck` with the current Go toolchain before you run a CI-equivalent scan: `go install golang.org/x/vuln/cmd/govulncheck@latest`. A binary built by an older toolchain diverges from what CI sees. - Make the blocking CI job fail only on reachable, symbol-level vulnerabilities. Keep module-level findings informational. - An unmaintained module that an OSV entry marks fully affected, with no fixed version, can never be cleared. A version bump only pins the last tag. The finding stays open forever. - A vulnerability database can mark a fixed release as affected. GO-2026-6872 and GO-2026-6875 credit the `echo/v4` fix only to v5, while v4.16.3 does contain the fix and the GHSA range ends at 4.16.2. `govulncheck` then flags more than 200 symbols even on the patched release. Verify the advisory's affected range and the upstream fix before you bump. The failure is a database defect, not a code problem (golang/vulndb#6924). ## How to apply 1. Place `//nolint` directives on the line above the target declaration. 2. In each module directory, run `golangci-lint run --fix`, then run `goimports` on the changed files. 3. Rebuild the scanner with the current toolchain, then scan: `go install golang.org/x/vuln/cmd/govulncheck@latest` and `govulncheck ./...`. 4. Run custom analyzers with `go vet -vettool=/path/to/analyzer ./...`. 5. Before a dependency bump for a vulnerability, check the advisory's affected range against the upstream fix. A finding that survives a bump to the latest tag is a database or maintenance signal, not a code problem. 6. Count the call sites of a function before you add a new one, when `unparam` is active. ## Observations - [tooling] Directives placed in doc-comment position stop working in golangci-lint v2. `nolintlint` flags them unused while the linter still fires. #golangci-lint (source: agent sessions, 2026-07) - [tooling] `gofmt` re-aligns struct-field comment groups and undoes manual padding, which breaks format checks. #gofmt (source: agent sessions, 2026-02) - [tooling] `unparam` hides a call-site threshold of 4. The fourth call site flips CI lint from green to failing. #unparam (source: agent sessions, 2026-10) - [tooling] Root-module lint runs miss sub-module violations in multi-`go.mod` repositories. #golangci-lint (source: agent sessions, 2026-07) - [security] `govulncheck` flags only reachable vulnerabilities. This gives precise CI blocking with far fewer false positives than module-level scanners. #govulncheck (source: agent sessions, 2026-03) - [security] An unmaintained module with no fixed version can never be cleared by a dependency bump. #vulnerability-management (source: agent sessions, 2026-09) - [security] A vulnerability database entry can mark a patched release as affected. Verify the advisory range before you trust a failing scan. #vulnerability-management (source: agent sessions, 2026-11) ## Relations - relates_to [[resources/documentation/Go/Go - Modules, Workspaces, and GOPROXY]] (per-module lint runs and dependency bumps in multi-module repositories) - relates_to [[resources/documentation/Go/Go - Toolchain Pinning and the Build Cache]] (rebuilding govulncheck with the current toolchain) - relates_to [[resources/documentation/Renovate and Dependabot - Automation Semantics]] (module-level scanning versus call-graph analysis) - relates_to [[resources/documentation/Go/Go - Correctness Idioms]] (identifier naming and validation-helper structure) ## Sources - Personal agent-session notes, February 2026 to November 2026