Skip to content

build: add golangci-lint, nilaway and race gate - #156

Merged
noamsto merged 4 commits into
mainfrom
feat/155-add-golangci-lint-nilaway-and-race-gate
Sep 29, 2026
Merged

noamsto merged 4 commits into
mainfrom
feat/155-add-golangci-lint-nilaway-and-race-gate

Conversation

@noamsto

@noamsto noamsto commented Sep 29, 2026

Copy link
Copy Markdown
Owner

Closes #155

Summary

Add a stricter Go static-analysis gate and fix what it finds.

  • .golangci.yml (v2) enables exactly errcheck, govet, staticcheck,
    unused, ineffassign, exhaustive, errorlint, nilerr, bodyclose,
    gosec and modernize, with exhaustive: default-signifies-exhaustive: true.
    gosec is excluded for *_test.go (owner decision); it stays on for production
    code.
  • nilaway runs over the module's own packages; go test -race ./... runs the
    race detector.
  • All three are checks.<system>.*, so nix flake check enforces them; the
    devShell gains nilaway, and CI builds the three checks.

Plan: task doc (provided)

Findings tally

modernize findings are one grouped row per analyzer; identical sites are
grouped.

linter file:line class action would Rust's compiler have rejected it?
nilaway internal/cache/cache.go:108 real bug fixed — a JSON null cache file unmarshalled to a nil map, so the next Set panicked; guard + regression test yes — Rust has no nil map and the flow is compile-checked
gosec G602 internal/ui/box.go:421 real bug fixed — reject empty tabs before indexing the active tab no — bounds checks are runtime in Rust too
errcheck internal/action/handoff.go:19 latent risk fixed — return the Close error instead of discarding it no — #[must_use] warns at most
errcheck internal/cache/cache.go:195–204 latent risk fixed — _ = on best-effort close/remove in an error path no
bodyclose internal/gh/ratelimit_test.go:280,297 latent risk fixed — close the stub response body no
exhaustive internal/ui/cascade_test.go:340 latent risk fixed — added the missing cascadeSettle arm yes — an exhaustive enum match must cover every variant
gosec G301 internal/cache/cache.go:186 latent risk fixed — cache dir 0750, not world-readable 0755 no
gosec G115 internal/gh/graphql.go:160, issues_graphql.go:57,109 latent risk fixed — clamp int→int32 through one gqlInt helper partly — as truncates silently; i32::try_from forces handling
nilaway internal/ui/{box,held,prlist,section,logview}.go, internal/gh/prs.go false positive fixed narrowly — normalize cached/section slices to non-nil at their source; bounds guards the analyser could not derive no
nilaway internal/ui/actions.go:868, prlist.go:3003, internal/gh/ratelimit.go:124 false positive fixed narrowly — nil guards on the pending action/plan and the transport request no
nilaway 17 test sites false positive fixed narrowly — len/error checks before indexing test results no
errcheck main.go:27,36,38 false positive suppressed — best-effort notice before os.Exit(1) no
gosec G304/G204/G115 handoff.go:15, token.go:41, main.go:91, theme.go:146 false positive suppressed narrowly — intended file/subprocess argument or a bounded palette length no
gosec (test files) 10 sites (G101/G204/G301/G304/G306) false positive excluded for *_test.go by owner decision; three fixture permissions tightened in passing no
modernize 13 sites style fixed — --enable-only modernize --fix (strings.Cut/Seq, range-int, min/max) no
staticcheck 4 sites style fixed — De Morgan simplification, fmt.Fprintf over WriteString(Sprintf) no
unused internal/ui/section.go:496,525 style fixed — removed dead groupByCategory/groupByAuthor no

Summary: 81 findings: 2 real bugs (1 Rust-catchable), 9 latent, 51 false positives, 19 style.

Testing

  • golangci-lint run ./... → 0 issues.
  • nilaway -include-pkgs=github.com/noamsto/prdash ./... → no findings (exit 0)
  • go test -race ./... → all packages ok (exit 0)
  • nix build .#checks.x86_64-linux.golangci-lint .#checks.x86_64-linux.nilaway .#checks.x86_64-linux.go-test-race → exit 0
  • existing gate: go build ./..., go vet ./..., go test ./..., nix fmt → exit 0
  • regression: go test ./internal/cache -run TestOpenNullFileStaysWritable — panics (nil-map write) without the guard, passes with it

Review notes

  • _test.go gosec exclusion — refuted: an explicit owner/dispatcher decision
    for this task that overrides the task doc's general no-exclusion rule, and the
    only path exclusion allowed; gosec stays on for production code.
Agent ledger

Acceptance ledger

  • AC1 pass — golangci-lint, nilaway and go test -race all green, locally and as checks.<system>.* (commands above).
  • AC2 pass — findings tally table above.
  • AC3 pass — summary line above and in the pr_open detail.
  • AC4 pass — existing tests/lint/build green (commands above).

Review ledger

invariant/family finding or thread IDs observed head fix commit proof disposition rounds used
ci-action-pinning reviewer round 1 HIGH .github/workflows/ci.yml:13 a8ad6f0 8284bab both actions pinned to full SHAs; round 2 re-review accept fixed 1
gosec-test-exclusion reviewer round 1 MEDIUM .golangci.yml:24 a8ad6f0 — explicit owner/dispatcher directive overriding the task doc's general no-exclusion rule refuted 1

recurrence_escalation: unused

Add a stricter Go static-analysis gate and resolve what it finds:

- .golangci.yml (v2) enabling exactly errcheck, govet, staticcheck, unused,
  ineffassign, exhaustive, errorlint, nilerr, bodyclose, gosec and modernize.
- nilaway over the module's own packages, and `go test -race ./...`.
- Wire all three into `checks.<system>.*`, add nilaway to the devShell, and run
  the gate in CI.

Fixes: a real nil-map write when the cache file holds JSON `null`; tighter
cache file/dir perms; exhaustive switch on the cascade test pump; out-of-range
tab index; unchecked file-close errors; int->int32 GraphQL narrowing; nil-slice
normalization; modernize auto-fixes. Narrow gosec suppressions for intended
subprocess/file-inclusion cases.
Per owner decision, gosec runs on production code only:
`linters.exclusions.rules` skips `_test\.go`. Drop the now-redundant
`//nolint:gosec` directives those test fixtures carried.
Pin actions/checkout and DeterminateSystems/nix-installer-action to full
commit SHAs (with a version comment), so a moved or compromised tag cannot
execute in CI with the workflow token.
The SetPRs/SetIssues normalization already keeps the section slices
non-nil, so the nilaway-driven length guards on numberAt no longer earn
their keep; restore the original one-liners.
@noamsto
noamsto merged commit a607872 into main Sep 29, 2026
1 check passed
@noamsto
noamsto deleted the feat/155-add-golangci-lint-nilaway-and-race-gate branch September 29, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add golangci-lint, nilaway and race gate

1 participant