Skip to content

ci: enforce that the two golangci-lint pins agree - #5235

Open
bryanbeverly wants to merge 2 commits into
mainfrom
bryanbeverly/cursor/enforce-golangci-lint-pin-sync
Open

ci: enforce that the two golangci-lint pins agree#5235
bryanbeverly wants to merge 2 commits into
mainfrom
bryanbeverly/cursor/enforce-golangci-lint-pin-sync

Conversation

@bryanbeverly

Copy link
Copy Markdown
Contributor

Note

Authorship: this pull request description, and the code changes it describes, were drafted by Cursor (an AI coding assistant) on behalf of @bryanbeverly, who reviewed them before opening the PR.

What changed

Adds scripts/check_lint_pins.py and a lint-pin-consistency job in .github/workflows/lint.yml that runs it.

golangci-lint is pinned twice — as the version input to golangci-lint-action in .github/workflows/lint.yml, and as GOLANGCI_LINT_VERSION in scripts/lint.sh. Both files say the two must match. Nothing enforced it.

The check does two things:

  1. Asserts the pins are equal and concrete. This catches drift however it arrives, including a hand edit Renovate was never involved in, and rejects floating values like latest or a bare v2 that read like a pin but aren't.
  2. Applies the regex from .github/renovate.json to scripts/lint.sh and requires it to match. It also checks the datasource, the target file, and the grouping rule.

Why it changed

The pins have drifted twice: v2.12.2 in the workflow against v2.11.4 in the script, then v2.13.1 against v2.12.2. The second was more than cosmetic — go1.27 support arrived in golangci-lint v2.13.0, so make lint on the stale pin failed outright for anyone on that toolchain.

1bc0db6b5 fixed the bumping gap with a Renovate custom manager. This PR addresses the quieter gap that fix introduced: a regex that stops matching fails open. Renovate reports no dependency rather than an error, so reshaping that assignment — single quotes, a readonly wrapper, interpolation — would silently resume bumping the workflow alone, with nothing to notice. Since the custom manager is currently the only thing holding these two files together, that failure mode is the whole exposure rather than an inconvenience.

Worth noting where this came from: the sibling repo slack-integration-service has the same double-pin, and a guard comparing the two pins has been catching drift there since dc636205. Its Renovate PRs go red rather than quietly wrong. This ports that backstop here.

Design notes

  • Standard library only, and no YAML parsing. The workflow pin is read as text. PyYAML is not installed on a bare ubuntu-latest runner, and this check shouldn't need a dependency install step to run.
  • Python rather than a shell script, because the job is JSON parsing plus a PCRE named-group regex. scripts/test/diff_corpora_results.py is the precedent for a Python helper invoked from CI.
  • One translation is applied to Renovate's regex before use: (?<currentValue> becomes (?P<currentValue>. Renovate matches with RE2, which accepts both spellings; Python's re accepts only the latter. Renaming the group is the entire translation and does not change what the pattern matches.
  • managerFilePatterns is honored in both spellings — a slash-wrapped regex or a glob — rather than assuming the form currently in the config.
  • Both NOTE comments now name the enforcing check, and say plainly that the version half is enforced while the args half is not.

Deliberately not in this PR

The NOTE comments claim "Version and args must match." Only the version is enforced here. LINT_ARGS (--enable bodyclose,copyloopvar,misspell --timeout 10m) is duplicated across the same two files with no bot in the loop and no check, so it can still drift silently. Worth a follow-up; it's a different kind of comparison and didn't belong in the same change.

How it was validated

Nine cases, each confirmed to fail for the intended reason or pass when it should:

Case Expected
Workflow bumped, script left behind fail
Script pin reshaped to single quotes fail
Script pin wrapped in readonly (still tracked) pass
customManager deleted fail
Grouping packageRule deleted fail
Manager retargeted at the wrong file fail
Workflow pin floated to latest fail
Regex-form managerFilePatterns pass
Clean baseline pass

The single-quote case is the one that matters most: it's the fail-open scenario, and it's now caught.

actionlint is clean on the modified workflow.

Made with Cursor

golangci-lint is pinned twice, as the version input to golangci-lint-action in
.github/workflows/lint.yml and as GOLANGCI_LINT_VERSION in scripts/lint.sh. Both
files carry a NOTE comment saying the two must match, and until now nothing
enforced it. The pins drifted twice: once leaving the workflow on v2.12.2 while
the script stayed on v2.11.4, and again leaving the script on v2.12.2 after the
workflow moved to v2.13.1. The second drift was more than cosmetic, since go1.27
support arrived in golangci-lint v2.13.0 and `make lint` on the stale pin failed
outright for anyone on that toolchain.

The custom manager added in 1bc0db6 closed the bumping gap, but it introduced a
quieter one. A regex that stops matching fails open: Renovate reports no
dependency rather than an error, so reshaping the assignment - single quotes,
a readonly wrapper, interpolation - would silently return us to bumping the
workflow alone, with nothing to notice. The custom manager is currently the only
thing keeping these two files together, which makes that failure mode the whole
exposure rather than an inconvenience.

scripts/check_lint_pins.py therefore checks both halves. It asserts the pins are
equal and concrete, which catches drift however it arrives, including a hand edit
Renovate was never involved in. It then applies the regex configured in
.github/renovate.json to scripts/lint.sh and requires it to match, so the manager
cannot quietly stop tracking the file it exists to track. It also confirms the
grouping rule still routes both pins into one PR.

A lint-pin-consistency job runs it, alongside the existing man-page-staleness
job that plays the same repo-hygiene role. The check uses only the Python
standard library and reads the workflow pin as text rather than parsing YAML,
because PyYAML is not present on a bare runner.

Validated with nine cases, each confirmed to fail for the intended reason and
pass when it should: real drift, a single-quoted pin, a readonly wrapper that
should still be tracked, a deleted manager, a deleted group rule, a manager
retargeted at the wrong file, a workflow pin floated to latest, the regex
spelling of managerFilePatterns, and a clean baseline. actionlint is clean on
the modified workflow.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7963209. Configure here.

Comment thread .github/workflows/lint.yml Outdated
The job invoked ./scripts/check_lint_pins.py, which needs the git executable bit
to survive. The bit is committed correctly, so this was not failing, but the
dependency is unnecessary and the invocation is inconsistent with the repository:
shell helpers here are run as ./scripts/..., while the one Python helper is run as
`python3 scripts/test/diff_corpora_results.py` in detector-corpora-test.yml.

Invoking through python3 matches that precedent and makes the job indifferent to
the mode bit. Verified by stripping the bit locally: the python3 invocation still
exits 0, while ./scripts/check_lint_pins.py exits 126 with permission denied. The
shebang and executable bit are kept so the script stays directly runnable.

Co-authored-by: Cursor <cursoragent@cursor.com>
@bryanbeverly
bryanbeverly marked this pull request as ready for review August 25, 2026 19:23
@bryanbeverly
bryanbeverly requested a review from a team August 25, 2026 19:23
@bryanbeverly
bryanbeverly requested a review from a team as a code owner August 25, 2026 19:23
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