Skip to content

ci: verify golangci-lint config against a vendored schema - #1016

Merged
cristim merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-977
Oct 16, 2026
Merged

cristim merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-977

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Root cause

The Run golangci-lint step in the Lint Code job ran golangci-lint config verify before each lint run. With no --schema, golangci-lint v2.10.1 builds the URL https://golangci-lint.run/jsonschema/golangci.v2.10.jsonschema.json and fetches it with an HTTP client that has a hard-coded 2s timeout (newJSONSchemaHTTPLoader in pkg/commands/config_verify.go). When golangci-lint.run is slow to send headers, verify exits 3 and the job goes red even though the lint reports 0 issues. The first attempt of run 38894294427 (job 116950948944) shows this in Run golangci-lint:

==> golangci-lint in .
The command is terminated due to an error: [.golangci.yml] validate: compile schema: failing loading "https://golangci-lint.run/jsonschema/golangci.v2.10.jsonschema.json": ... context deadline exceeded (Client.Timeout exceeded while awaiting headers)

The golangci-lint-action step isn't involved: with install-only: true it returns before its own runVerify. golangci-lint run doesn't fetch the schema either.

Fix

The config is still checked against the schema on every run. The check just no longer needs the network.

  • The v2.10 schema now lives in the repo at .github/golangci-lint/golangci.v2.10.jsonschema.json. It was copied from jsonschema/golangci.next.jsonschema.json at tag v2.10.1, read from the Go module cache. That file is byte-identical to the published https://golangci-lint.run/jsonschema/golangci.v2.10.jsonschema.json and to jsonschema/golangci.v2.10.jsonschema.json on golangci-lint main. All three have sha256 692e8da79dc1f110a4a4f3bb841b26ed44e461d3503678eaaae90ae8876e3403. The schema's only refs are internal (#/...), and the draft-07 metaschema is built into the validator, so nothing is fetched.
  • A new step, Verify golangci-lint config against vendored schema, runs before Run golangci-lint. It:
    1. gets the major.minor of the installed binary from golangci-lint version --short, and fails if there's no matching golangci.v<major.minor>.jsonschema.json;
    2. checks the file against GOLANGCI_LINT_SCHEMA_SHA256 with sha256sum -c, so an accidental edit (formatter, hand edit) fails loudly;
    3. runs golangci-lint config verify --schema file://$PWD/<schema>.
  • The per-module config verify is removed from the lint loop. tests/e2e has no config of its own (golangci-lint config path there gives ../../.golangci.yml), so one check covers both modules.

Why this option and not the others in the issue: verify: false on the action wouldn't help, because the action doesn't verify in install-only mode, and dropping the check entirely would lose validation. A non-blocking retry would still depend on the network and would no longer block a bad config. Vendoring is deterministic and keeps the check blocking. No new actions, and the existing action SHAs are unchanged.

Caveat: --schema is a hidden flag. Upstream says it's "for debugging purpose only", but it has been there since config verify was added. If a future version drops it, this step fails loudly on the bump PR; it won't silently skip the check.

Keeping the schema current

When version: on the golangci-lint-action step moves to a new minor, the new step fails with no vendored schema for golangci-lint X.Y.Z until the schema is updated. The steps are also in the step's comment in ci.yml:

  1. curl -fsSLo .github/golangci-lint/golangci.vX.Y.jsonschema.json https://golangci-lint.run/jsonschema/golangci.vX.Y.jsonschema.json
  2. Delete the old golangci.v2.10.jsonschema.json.
  3. Set GOLANGCI_LINT_SCHEMA_SHA256 to the new file's sha256sum.

A patch bump (for example 2.10.2) keeps the same schema file.

Closes #977

Verification

All local, macOS, golangci-lint v2.10.1 (go1.26.9). The new step's run: block was pulled out of ci.yml and run as-is. Network was blocked with HTTPS_PROXY=HTTP_PROXY=http://127.0.0.1:9, which Go's HTTP client honors.

Check Result
Before: old golangci-lint config verify, network blocked exit 3, failing loading "https://golangci-lint.run/jsonschema/golangci.v2.10.jsonschema.json" ... connection refused. Reproduces the CI failure.
After: new step, network blocked, real .golangci.yml ...: OK from the sha256 check, then verify exit 0. -v shows with the JSON Schema from file:///.../.github/golangci-lint/golangci.v2.10.jsonschema.json.
After, network blocked, not-a-real-key: true added exit 3, additional properties 'not-a-real-key' not allowed
After, network blocked, linters.default: bogus-default exit 3, value must be one of 'standard', 'all', 'none', 'fast'
After, one byte added to the vendored schema exit 1, sha256sum: ... FAILED
After, no schema for the installed minor exit 1, ::error::no vendored schema for golangci-lint 2.10.1 ...
Old online verify on the real config (parity) exit 0
Full Run golangci-lint block, network blocked, GOPROXY=off 0 issues. for . and for tests/e2e, exit 0. run makes no schema fetch.
actionlint 1.7.12 (shellcheck enabled) exit 0
zizmor==1.29.0 --offline --persona=pedantic --min-severity=medium .github/workflows/ (as Lint Workflows runs it) No findings
shellcheck -s bash on the extracted new and changed run: blocks clean
git-secrets 1.3.0 --register-aws on a temp copy (isolated HOME), scanning both changed files exit 0
bash scripts/test-git-secrets-allowlist.sh 42 PASS, 0 FAIL
Vendored JSON parses, ends with a newline, no trailing whitespace or CR (check-json, end-of-file-fixer, trailing-whitespace); 170 KB, under the 1024 KB large-file limit ok

scripts/test-actionlint-hook.sh wasn't run because it needs pre-commit, which isn't installed here. This PR doesn't touch that hook.

The changed workflow only runs on GitHub once a maintainer approves the workflow run for this fork PR. Until then, the evidence above is the only check of the new step on this PR.

Out of scope

  • Adding an offline config verify to make lint or pre-commit. CI still blocks on it; this can follow up if wanted.
  • Checking that version: v2.10.1 in ci.yml matches GOLANGCI_LINT_VERSION in the Makefile. They agree today.
  • Behavior change: an invalid config now fails in its own step, before Run golangci-lint. Previously the loop went on to lint both modules and reported the failure at the end. The job is red either way.

🤖 Generated with Claude Code

`golangci-lint config verify` fetched its JSON schema from
golangci-lint.run on every Lint Code run, with a hard-coded 2s HTTP
timeout, so the job failed at random before linting anything.

Vendor the v2.10 schema (sha256 692e8da7..., byte-identical to the
published https://golangci-lint.run/jsonschema/golangci.v2.10.jsonschema.json
and to jsonschema/golangci.next.jsonschema.json at tag v2.10.1) and pass
it to `config verify --schema file://...` in a dedicated step. The step
checks the file's sha256 and fails if the installed golangci-lint minor
has no vendored schema, so a version bump cannot silently validate
against a stale schema. The config is still validated on every run;
it just no longer needs the network.

Closes LeanerCloud#977

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 2 billable files and costs up to $0.50.

Or wait 57 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e52a539c-8995-47ed-b3de-48ba0f437600
📥 Commits

Reviewing files that changed from the base of the PR and between 0a5f790 and 7ec5f56.

📒 Files selected for processing: 2
  • .github/golangci-lint/golangci.v2.10.jsonschema.json
  • .github/workflows/ci.yml
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@IAmJSD IAmJSD added priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-sprint Within the current sprint impact/internal Team-internal only effort/s Hours type/bug Defect triaged Item has been triaged labels Oct 16, 2026
@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

Independent review verdict: CLEAN at 7ec5f56

Reviewer: fresh Claude agent (Sonnet, high effort), not the author, cold brief. The PR body was treated as a claim. Coordinator: claude-cc-reviewer.

Blocking findings: none.

Provenance: the vendored schema has sha256 692e8da79dc1f110a4a4f3bb841b26ed44e461d3503678eaaae90ae8876e3403.

  • It is identical to upstream jsonschema/golangci.next.jsonschema.json at tag v2.10.1, and to https://golangci-lint.run/jsonschema/golangci.v2.10.jsonschema.json.
  • There is no v2.10 file at the tag (it 404s), which the PR comment explains.
  • The CI pin is v2.10.1.

The step still fails on bad config: CI runs golangci-lint config verify --schema "file://$PWD/.github/golangci-lint/golangci.v2.10.jsonschema.json". --schema is hidden in --help but works.

  • The real config gives rc=0, and tests/e2e resolves the root config and passes too.
  • With bogus_key: true appended, it gives rc=3 ("additional properties 'bogus_key' not allowed").

ci.yml is not weakened:

  • The change: it adds the verify step and removes only config verify && from the per-module lint loop.
  • Nothing softened: there is no continue-on-error and no || true, and no job is removed or renamed. "CI Success" and "Run pre-commit hooks" are untouched.
  • The new step: it uses set -euo pipefail, fails if the schema file is missing, and runs sha256sum -c against a pinned hash.

Maintenance: the schema filename is derived from golangci-lint version --short, so bumping to a new minor version fails loudly until the new schema is vendored. A patch bump needs nothing.

pre-commit: all hooks pass or are skipped on the JSON file and ci.yml: JSON syntax, large files, EOF, whitespace, actionlint, zizmor and trivy.

Gate:

@IAmJSD
IAmJSD marked this pull request as ready for review October 16, 2026 23:15
@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

Gate record for 7ec5f56 (origin/main CLAUDE.md "Review gate"):

Merging at 7ec5f56 with --match-head-commit.

@cristim
cristim merged commit 9d006f3 into LeanerCloud:main Oct 16, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/internal Team-internal only priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: Lint Code flakes on golangci-lint jsonschema download timeout

2 participants