Skip to content

ci: lint the pull request diff instead of the whole module - #17

Merged
major0 merged 1 commit into
mainfrom
ci/lint-diff-against-pr-base
Sep 23, 2026
Merged

major0 merged 1 commit into
mainfrom
ci/lint-diff-against-pr-base

Conversation

@major0

@major0 major0 commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

The Pre-commit workflow already does the hard part: it checks out with
fetch-depth: 0, fetches main, and passes the pull request base and head to
pre-commit. The golangci-lint hook then ignored both.

The upstream dnephin hook declares pass_filenames: false and hard-codes:

golangci-lint run --new-from-rev HEAD --fix

In CI the checkout already sits at the pull request head with a clean tree, so
--new-from-rev HEAD diffs HEAD against itself. The baseline collapses, and
pre-existing findings anywhere in the module get attributed to whichever pull
request happens to touch a Go file. #12 and #13 were both blocked this way by a
finding in api/drive that neither touched.

That flag is correct for the hook's intended use — a local pre-commit run,
where HEAD is the right baseline for uncommitted work. It is simply wrong in
CI.

Changes

Replace the hook with a local one calling .github/scripts/lint_diff.py, which
takes its baseline from PRE_COMMIT_FROM_REF. pre-commit exports that variable
when invoked with --from-ref, so:

Context Baseline Effect
CI (--from-ref <base>) pull request base judged on its own diff
Local git hook HEAD (fallback) lints uncommitted work

One hook, correct in both environments. This deliberately does not switch to
linting every file on every commit — the goal is that the refs the workflow
already sets up become load-bearing.

This follows the existing precedent in .pre-commit-config.yaml, where
go-unit-tests was already replaced by a local hook for the same class of
reason: an upstream hard-coded flag that does not suit this repository.

Also ignore Python bytecode, now that hook scripts live in the repository.

Why Python rather than shell

pre-commit requires Python, so it is guaranteed present. A POSIX shell is not:
this module builds for windows/amd64 and windows/arm64, so contributors may
be developing on Windows. shutil.which resolves the .exe suffix, so there is
no platform special casing.

Failure behaviour

The hook fails loudly when the baseline revision cannot be resolved — what a
shallow clone would produce — rather than silently degrading to a whole-module
lint, which is the behaviour it exists to prevent.

Testing

Reproduced CI against #12: checked out origin/pr/12, applied this commit on
top, and ran pre-commit exactly as the workflow does.

pull request ref : origin/pr/12 (bc29d28)
baseline         : origin/main (d1dbea3)
files changed    : api/lumo/chat_completions.go
                   api/lumo/client.go
                   api/lumo/client_test.go

golangci-lint............................................................Passed
CONFIRMED: the pre-existing api/drive finding was not reported

The hook ran and passed rather than being skipped, so this is not a vacuous
result. #12's own new code also lints clean under golangci-lint 2.13.2.

Also verified that language: python builds its hook environment correctly, and
that the hook still respects types: [go] — it is skipped on commits touching
no Go files.

Related

This is what unblocks #12 and #13. #16 removes the api/drive dead code on its
own merit, but is not required for either to go green.

Still outstanding: golangci-lint is pinned in two places that disagree —
lint.yml requests v2.11.4 while install-go-tools.sh pins v2.13.2 — which is
why Lint and Pre-commit can reach opposite verdicts on identical code. That
is a separate PR.

The pre-commit workflow fetches full history and passes the pull request
base and head to pre-commit, but the golangci-lint hook never used either.
The upstream dnephin hook declares pass_filenames: false and hard-codes:

    golangci-lint run --new-from-rev HEAD --fix

In CI the checkout is already at the pull request head with a clean tree, so
--new-from-rev HEAD diffs HEAD against itself. The baseline collapses and
pre-existing findings anywhere in the module are attributed to whichever
pull request happens to touch a Go file. Two contributor pull requests were
blocked this way by a finding in api/drive that neither of them touched.

Replace the hook with a local one calling lint_diff.py, which takes its
baseline from PRE_COMMIT_FROM_REF. pre-commit exports that variable when
invoked with --from-ref, so CI compares against the merge target and a pull
request is judged solely on its own diff. Run from a git hook the variable
is unset and the script falls back to HEAD, which is the correct baseline
there because the developer is linting uncommitted work.

This keeps one hook behaving correctly in both environments rather than
linting every source file on every commit, and it makes the refs the
workflow already sets up actually load-bearing.

The hook is Python rather than shell. pre-commit requires Python, so it is
guaranteed present, whereas a POSIX shell is not: this module is built for
windows/amd64 and windows/arm64 and contributors may be developing there.
shutil.which resolves the .exe suffix, so no platform special casing is
needed.

The hook fails loudly when the baseline revision cannot be resolved, which
is what a shallow clone would produce, rather than silently degrading to a
whole-module lint.

Ignore Python bytecode, since the hook scripts now live in the repository.

Assisted-by: Kiro <noreply@kiro.dev> (1.0.116)
@major0
major0 force-pushed the ci/lint-diff-against-pr-base branch from a8f09df to 7e3e2cb Compare September 23, 2026 13:39
@major0
major0 merged commit e1c0f2a into main Sep 23, 2026
9 of 10 checks passed
@major0
major0 deleted the ci/lint-diff-against-pr-base branch September 23, 2026 13:44
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