Skip to content

ci(vendor-hash): fix fake-hash leak; batch indirect Go deps#976

Merged
cpcloud merged 2 commits intomicasa-dev:mainfrom
cpcloud:ci/fix-vendor-hash-and-batch-indirects
Apr 23, 2026
Merged

ci(vendor-hash): fix fake-hash leak; batch indirect Go deps#976
cpcloud merged 2 commits intomicasa-dev:mainfrom
cpcloud:ci/fix-vendor-hash-and-batch-indirects

Conversation

@cpcloud
Copy link
Copy Markdown
Collaborator

@cpcloud cpcloud commented Apr 23, 2026

Fixes accumulated while watching Renovate shake this out end-to-end (PRs #974 and #977).

What went wrong

update-vendor-hash.yml committed a sentinel hash (PR #974). The compute step writes sha256-AAAA... into nix/package.nix, runs nix build, parses got: sha256-.... On the old_hash == new_hash branch it was exiting before the final sed restored the real hash, leaving the sentinel in the file. go mod tidy still touched go.mod/go.sum, so git diff --cached was non-empty and Commit and push ran with the sentinel. Verify build was gated on changed == 'true', so nothing caught it. See run 24829437487: vendorHash unchanged (sha256-wDz1EKWKPkubV8NcBGQnLnRi4XT0rCjOQBKtO/yRdds=), nothing to do followed by [renovate/major-go-indirect ee7cceb] chore: update vendorHash for Go dependency changes.

Renovate kept opening major-version indirect bumps (PRs #974, #977). github.com/openai/openai-go and modernc.org/libc got proposed v1 -> v3 and v1 -> v2 updates. go mod why github.com/openai/openai-go/v3 returns (main module does not need package ...) -- the new major is a different module path and nothing in the repo imports it, so go mod tidy strips the lines every time. The PR never reconciles with main.

The batched go-indirect run (#977) then tripped two pre-commit hooks as false positives. After go mod tidy reshapes go.sum without altering vendor contents, Commit and push hits vendor-hash-check ("go.sum changed but nix/package.nix is unchanged") and go-mod-tidy ("tidy would modify files" -- the upstream Tidy go modules step already tidied).

Changes

  • .github/workflows/update-vendor-hash.yml
    • Move the restoration sed ahead of the unchanged-check so the file always holds a real hash before any exit.
    • | head -n1 on the grep to defend against multi-line got: output.
    • SKIP: vendor-hash-check,go-mod-tidy env on Commit and push. Narrow per-hook skip via the prek SKIP variable, scoped to the step that the workflow is itself the authority for. All other hooks still run.
  • renovate.json
    • separateMajorMinor: false, separateMultipleMajor: false, separateMinorPatch: false on the go-indirect rule so every indirect update lands in one PR regardless of semver.
    • New rule disabling matchUpdateTypes: ["major"] for indirect deps under gomod -- stops the doomed major-bump PRs at the source.

Reproduction

  1. Merge a real dep change that only touches go.sum line checksums (no vendor-content delta).
  2. Open any Renovate PR that only bumps an unused indirect dep to a new major version.
  3. Observe: before this PR, (1) leads to a sentinel-hash commit and (2) leads to an unfixable PR that go mod tidy keeps reverting. After this PR: (1) restores the real hash cleanly and (2) never gets opened.

Follow-up

Close #974 and #977 -- their branches carry the broken commits and Renovate will open fresh batched non-major PRs under the new rules.

cpcloud added 2 commits April 23, 2026 07:19
The workflow replaces vendorHash with a fake sentinel before calling
nix build, parses the real hash from the "got:" line, and substitutes
it back. When old_hash equals new_hash, the early-return branch was
exiting before performing that final substitution, leaving the fake
hash in nix/package.nix. Combined with go.mod/go.sum changes from
go mod tidy, the Commit and push step then committed the fake hash.
See PR micasa-dev#974 for a concrete occurrence.

Move the restoration sed ahead of the unchanged check so the file is
always in a valid state before any exit. Also pipe grep through
head -n1 to defend against multi-line output (e.g. multiple FOD
failures reporting their own got: lines).
Disable separateMajorMinor, separateMultipleMajor, and separateMinorPatch
on the go-indirect packageRule so Renovate collapses every indirect
Go dependency update into one branch/PR regardless of semver level.
Today Renovate splits indirects across separate branches per update
type (e.g. renovate/major-go-indirect), producing per-severity PRs
that churn nix/package.nix individually. Batching them is lower
maintenance noise and lets go mod tidy settle the whole set once.
@cpcloud cpcloud added ci CI/CD pipeline changes chore Maintenance and housekeeping dependencies labels Apr 23, 2026
@cpcloud cpcloud enabled auto-merge (squash) April 23, 2026 11:24
@cpcloud cpcloud merged commit 62422ea into micasa-dev:main Apr 23, 2026
28 checks passed
cpcloud added a commit that referenced this pull request Apr 23, 2026
…ooks (#978)

Follow-up to #976. PR #977 surfaced two gaps the first round didn't
address:

**1. Renovate keeps opening doomed major bumps for indirect Go deps.**
#977 proposed `github.com/openai/openai-go` v1 -> v3 and
`modernc.org/libc` v1 -> v2. Both new majors are different module paths,
nothing in this repo imports them, and `go mod why
github.com/openai/openai-go/v3` returns `(main module does not need
package ...)`. `go mod tidy` strips them on every workflow run. The PR
can never reconcile with main. Disable `matchUpdateTypes: ["major"]` for
`indirect` deps under `gomod` so these PRs stop being opened.

**2. The batched go-indirect workflow commit trips two pre-commit hooks
as false positives.** When `go mod tidy` reshapes `go.sum` without
altering vendor contents (exactly the state after tidy strips an unused
major), `Commit and push` fails:
- `vendor-hash-check` reports "go.sum changed but nix/package.nix is
unchanged" -- but the workflow already ran the authoritative `nix build`
hash computation in the previous step.
- `go-mod-tidy` reports "tidy would modify files" -- the upstream `Tidy
go modules` step already tidied.

Scope a `SKIP: vendor-hash-check,go-mod-tidy` env to just the `Commit
and push` step. This is a narrow per-hook skip via prek's `SKIP`
variable, not a blanket bypass. All other hooks still run on that
commit.

## Reproduction

1. Open any Renovate PR that bumps an indirect Go dep to a new major
version (the new major is a different module path and nothing imports
it).
2. Before this PR: the PR is unfixable, `go mod tidy` keeps reverting
the bump, and the vendor-hash workflow cannot commit its tidy output
because `vendor-hash-check` blocks. After this PR: Renovate never opens
the PR in the first place, and if an equivalent tidy-only scenario
arises from another path the workflow can still commit.

## Follow-up

Close #977 -- its branch carries the stuck state and Renovate will skip
that update-type under the new rule.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore Maintenance and housekeeping ci CI/CD pipeline changes dependencies

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant