Skip to content

ci: run the full prek hook suite as the gate - #58

Merged
min0625 merged 1 commit into
mainfrom
ci/prek-hook-suite
Aug 26, 2026
Merged

min0625 merged 1 commit into
mainfrom
ci/prek-hook-suite

Conversation

@min0625

@min0625 min0625 commented Aug 26, 2026

Copy link
Copy Markdown
Owner

.pre-commit-config.yaml becomes the single source of truth for what gets checked. make check is now prek run --all-files --show-diff-on-failure, and check-tidy / lint / test live in there as hooks alongside prek's builtin hygiene checks and gitleaks — so git commit (staged) and CI (repo-wide) run the same suite.

Hooks that would have passed green without checking anything

Verified empirically in a scratch repo under CI conditions (clean checkout, nothing staged, not mid-merge):

Hook Without args With args
check-added-large-files a 900 KB file passes --enforce-all → caught
check-merge-conflict a file full of conflict markers passes --assume-in-merge → caught

Both consult git state instead of the file list prek hands them, and CI's checkout has neither. The rationale is in a comment at the top of the config so the next hook added gets read before it gets trusted.

gitleaks keeps its hard-coded --staged entry on purpose — it is a local commit-time guard, not a CI secret scan. GitHub secret scanning push protection (enabled on this repo) is the server-side net. Documented inline and in AGENTS.md rather than silently left as a green no-op.

Other correctness details

  • always_run: true on the three local hooks — without it prek skips a hook whose file list comes back empty, so a commit that only deletes files would skip lint and test entirely.
  • Shared check-rev target: make lint and make fix now hard-fail on an unresolvable NEW_FROM_REV. golangci-lint only warns and exits 0 there, which would let CI pass having linted nothing. CI passes origin/${{ github.base_ref }}, and the guard was verified to propagate through make check → prek → the nested make lint.
  • repo: builtin uses prek's native Rust implementations — same checks, no repo clone or Python env built for code that never runs. Note this makes the config prek-only; upstream pre-commit rejects it with Missing required key: rev.
  • Added a ~/.cache/prek cache step so the from-source gitleaks build stays off the 15-minute job timeout.

Note on the .serena/ change

.serena/memories/memory_maintenance.md is unrelated to this change — it is the new repo-wide fixers' output on a pre-existing file (trailing whitespace, missing final newline). Kept deliberately: reverting it just moves the failure to the next unrelated PR that runs make check.

Verification

make check passes clean end to end, with no file rewritten.

🤖 Generated with Claude Code

.pre-commit-config.yaml becomes the single source of what gets checked:
`make check` is now `prek run --all-files`, and check-tidy/lint/test are
hooks in there alongside prek's builtin hygiene checks and gitleaks.

Hooks that would have run green without checking anything are pinned
down: check-added-large-files gets --enforce-all and check-merge-conflict
gets --assume-in-merge, since both otherwise consult git state (staged
files, mid-merge) that CI's clean checkout never has. gitleaks keeps its
--staged entry on purpose and is documented as a local commit-time guard,
with GitHub push protection as the server-side net.

The three local hooks carry always_run: true so a commit that only
deletes files can't skip lint and test on an empty file list. A shared
check-rev target makes `make lint` and `make fix` hard-fail on an
unresolvable NEW_FROM_REV -- golangci-lint only warns and exits 0 there,
which would let CI pass having linted nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@min0625
min0625 merged commit c69fa1b into main Aug 26, 2026
6 checks passed
@min0625
min0625 deleted the ci/prek-hook-suite branch August 26, 2026 10:10
min0625 added a commit that referenced this pull request Sep 1, 2026
Re-add gci. #59 dropped it as covered by gofumpt+goimports, but those two
only sort within the groups a file already has: a third-party import
stranded in the stdlib block is moved out into a group of its own rather
than merged into the existing third-party block. gci is what collapses it
back to the canonical two groups.

Replace gomodguard_v2's local-replace-directives with the gomoddirectives
linter. gomodguard only sees directly imported modules, so an indirect
dep's replace slipped through; gomoddirectives forbids every replace by
default -- checked against a resolvable non-local one, not just a
`=> ../local`.

Retire depguard. Its one rule (github.com/pkg/errors) moves into the
gomodguard_v2 blocklist so the recommendation text sits next to the other
deprecated modules. mitchellh/mapstructure and gopkg.in/yaml (prefix
match, so both majors) join it.

Enable bidichk, makezero and reassign. reassign gets patterns: ".*", which
is upstream's own recommendation -- the default only guards EOF and Err*,
leaving os.Args and http.DefaultClient open. Tests are excluded from it:
they borrow os.Stdin/os.Args and restore them afterwards.

Turn on errcheck.check-type-assertions and prealloc.for-loops (modernize
rewrites 3-clause loops into the range form prealloc polices, so with the
default the finding only surfaces after --fix), and turn off
perfsprint.concat-loop, which modernize rewrites better by reusing the
variable instead of inventing one.

Exclude gosec's G104. It fires on bare call statements only, never on
defer/go, and honors a whitelist -- a strict subset of errcheck, which
also names the offending function; with uniq-by-line: false both would
print on the same line. Narrowing errcheck (exclude-functions,
std-error-handling) would reopen the gap.

Correct the check-rev rationale in the Makefile and AGENTS.md. #58
documented golangci-lint as only warning and exiting 0 on an unresolvable
--new-from-rev, which would let CI pass having linted nothing. It does
not: it warns, reports every issue in the repo, and exits 1. The guard
stays, but it buys a readable error message, not safety.

`make fix` now runs tidy -> --fix -> tidy -> lint. --fix is not a
fixpoint: its edits can trip a linter the fixing run never saw, and can
add or remove imports. The new ordering was exercised on a clean tree, so
it confirms the plumbing runs, not that the second pass caught a real
fixpoint miss.

Also document in AGENTS.md that .golangci.yaml is not self-contained --
whole-files: true is silently inert without one of the --new* modes, so
the file only works alongside the Makefile's --new-from-rev harness --
and that formatter findings are the exception to the ratchet: apply
`golangci-lint fmt ./...` repo-wide rather than letting a half-formatted
tree contradict its own config.

`golangci-lint run ./...` reports 0 issues repo-wide under this config, so
the whole-files ratchet takes on no new debt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
min0625 added a commit that referenced this pull request Sep 1, 2026
Re-add gci. #59 dropped it as covered by gofumpt+goimports, but those two
only sort within the groups a file already has: a third-party import
stranded in the stdlib block is moved out into a group of its own rather
than merged into the existing third-party block. gci is what collapses it
back to the canonical two groups.

Replace gomodguard_v2's local-replace-directives with the gomoddirectives
linter. gomodguard only sees directly imported modules, so an indirect
dep's replace slipped through; gomoddirectives forbids every replace by
default -- checked against a resolvable non-local one, not just a
`=> ../local`.

Retire depguard. Its one rule (github.com/pkg/errors) moves into the
gomodguard_v2 blocklist so the recommendation text sits next to the other
deprecated modules. mitchellh/mapstructure and gopkg.in/yaml (prefix
match, so both majors) join it.

Enable bidichk, makezero and reassign. reassign gets patterns: ".*", which
is upstream's own recommendation -- the default only guards EOF and Err*,
leaving os.Args and http.DefaultClient open. Tests are excluded from it:
they borrow os.Stdin/os.Args and restore them afterwards.

Turn on errcheck.check-type-assertions and prealloc.for-loops (modernize
rewrites 3-clause loops into the range form prealloc polices, so with the
default the finding only surfaces after --fix), and turn off
perfsprint.concat-loop, which modernize rewrites better by reusing the
variable instead of inventing one.

Exclude gosec's G104. It fires on bare call statements only, never on
defer/go, and honors a whitelist -- a strict subset of errcheck, which
also names the offending function; with uniq-by-line: false both would
print on the same line. Narrowing errcheck (exclude-functions,
std-error-handling) would reopen the gap.

Correct the check-rev rationale in the Makefile and AGENTS.md. #58
documented golangci-lint as only warning and exiting 0 on an unresolvable
--new-from-rev, which would let CI pass having linted nothing. It does
not: it warns, reports every issue in the repo, and exits 1. The guard
stays, but it buys a readable error message, not safety.

`make fix` now runs tidy -> --fix -> tidy -> lint. --fix is not a
fixpoint: its edits can trip a linter the fixing run never saw, and can
add or remove imports. The new ordering was exercised on a clean tree, so
it confirms the plumbing runs, not that the second pass caught a real
fixpoint miss.

Also document in AGENTS.md that .golangci.yaml is not self-contained --
whole-files: true is silently inert without one of the --new* modes, so
the file only works alongside the Makefile's --new-from-rev harness --
and that formatter findings are the exception to the ratchet: apply
`golangci-lint fmt ./...` repo-wide rather than letting a half-formatted
tree contradict its own config.

`golangci-lint run ./...` reports 0 issues repo-wide under this config, so
the whole-files ratchet takes on no new debt.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant