Skip to content

fix(release): capture the lockfile baseline before anything can rewrite it - #2573

Open
glennmichael123 wants to merge 1 commit into
mainfrom
fix/release-lockfile-guard-baseline
Open

fix(release): capture the lockfile baseline before anything can rewrite it#2573
glennmichael123 wants to merge 1 commit into
mainfrom
fix/release-lockfile-guard-baseline

Conversation

@glennmichael123

Copy link
Copy Markdown
Member

The bug

bump.ts already guards against releasing a lockfile in the wrong format. It compares the version Bun just regenerated against the version already committed, and aborts when they disagree — with a carefully written message that names which side is stale.

The guard is correct. Its baseline was read too late.

277  pinLockstepDeps(nextVersion)      // rewrites hundreds of manifests
303  await refreshPantryLock()         // runs `pantry install` -> reaches for Bun
311  const previousLock = readFileSync(lockPath)   // <- baseline, AFTER the above
317  unlinkSync(lockPath)
319  bun install --lockfile-only
354  const expectedLockfileVersion = lockfileVersion(previousLock...)

refreshPantryLock() runs pantry install, which reaches for Bun and rewrites bun.lock once pinLockstepDeps has changed the manifests. So on a machine whose Bun predates the committed format, bun.lock was already downgraded by line 311 — and expected and produced were both the downgraded version. The guard compared the corrupted file against itself, passed, and let the release continue.

That is how v0.74.33 committed a lockfileVersion: 1 lockfile into a repository that requires v2, tagged it and pushed. CI failed on the release commit; the tag had to be deleted and the commit reverted.

The fix

Capture both lockfiles before the pantry refresh, and compare against that.

Also restore pantry.lock alongside bun.lock on every abort path. Only bun.lock was restored before, so a release that stopped here left a rewritten pantry.lock in the working tree — and on a machine that cannot download the pinned toolchain, that rewrite silently drops the bun.sh pin from it. All three aborts (failed regeneration, regeneration that wrote nothing, format mismatch) now go through one restoreCommittedLockfiles().

refreshPantryLock's own internal restore is untouched — it is correct and unrelated.

Verified end to end

Not just unit-tested. I ran the real ./buddy release --bump patch on the machine with the older Bun (1.3.14 against a repo pinning 1.4.1 in deps.yml), with remote.origin.pushurl pointed at a nonexistent path so nothing could escape if the fix were wrong.

Result:

Release aborted: regenerating bun.lock produced lockfileVersion 1, but the
committed one is v2 — this machine's Bun is older than the one that wrote the
committed lockfile. Re-run the release with the repository's declared Bun
toolchain through `pantry install`.
after the fix
exit code 1
release commit none created
tag none created
bun.lock restored to v2
pantry.lock restored — system block and all 4 bun.sh pins intact

Before this change, the identical command on the identical machine committed, tagged and pushed.

Tests

Two added to release-artifacts.test.ts, which reads bump.ts as source because it runs its release on import:

  • captures the lockfile baseline before anything can rewrite it — asserts by source position that the capture precedes both refreshPantryLock() and the regeneration, and that the comparison reads that capture rather than the working tree. This is the actual regression: nothing about the guard's own logic was wrong, only when its input was read.
  • every abort path restores both committed lockfiles — pins the shared restore and that no abort inside the block falls back to the single-file version.

The existing preserves the canonical lockfile format during a release test pins the exact expectedLockfileVersion line, so I kept the previousLock name rather than renaming through it.

Checks

  • release-artifacts 9 pass / 0 fail, release-preflight 13 pass / 0 fail
  • ./buddy lint — exit 0, 0 errors and 0 warnings across 3449 files
  • bun run typecheck — 13 pre-existing errors, none in bump.ts. That is two fewer than the 15 I measured yesterday; both differences are unrelated resolution errors (stx generateCss, ts-css/engine) that cleared when the verification runs' bun install refreshed node_modules, not anything this change did.

Note

This does not make releases possible from a machine with the wrong toolchain — it makes them fail there instead of shipping something broken. Releasing still needs Bun 1.4.1 and a working pantry install; on this machine pantry install reports bun.sh@1.4.1 (DownloadFailed), which is a separate problem.

🤖 Generated with Claude Code

…te it

bump.ts guards against releasing a lockfile in the wrong format: it compares
the version bun regenerated against the version already committed, and aborts
when they disagree. The guard is correct. Its baseline was read too late.

`refreshPantryLock()` runs `pantry install`, which reaches for Bun and rewrites
bun.lock once `pinLockstepDeps` has changed hundreds of manifests. The baseline
was read after that call, so on a machine whose Bun predates the committed
format both sides of the comparison were the already-downgraded version. The
guard passed on exactly the lockfile it exists to reject.

That is how v0.74.33 committed a lockfileVersion 1 lockfile into a repository
that requires v2, tagged it, and pushed - CI failed on the release commit, and
the tag had to be deleted and the commit reverted.

Read both lockfiles before the pantry refresh and compare against that.

Also restore pantry.lock alongside bun.lock on every abort. Only bun.lock was
restored before, so a release that stopped here left a rewritten pantry.lock in
the working tree - and where the pinned toolchain cannot be downloaded, that
rewrite silently drops the `bun.sh` pin from it.

Verified end to end on a machine with the older Bun, with pushes blocked at the
remote: the release now aborts with

  Release aborted: regenerating bun.lock produced lockfileVersion 1, but the
  committed one is v2 - this machine's Bun is older than the one that wrote the
  committed lockfile.

exits 1, creates no commit and no tag, and leaves bun.lock at v2 and
pantry.lock with its system block intact. Before this change the same command
on the same machine committed, tagged and pushed.
@github-actions github-actions Bot added actions @stacksjs/actions storage @stacksjs/storage core labels Sep 9, 2026
@pkg-pr-new

pkg-pr-new Bot commented Sep 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@stacksjs/action-runner

npm i https://pkg.pr.new/@stacksjs/action-runner@2573

@stacksjs/actions

npm i https://pkg.pr.new/@stacksjs/actions@2573

@stacksjs/ai

npm i https://pkg.pr.new/@stacksjs/ai@2573

@stacksjs/alias

npm i https://pkg.pr.new/@stacksjs/alias@2573

@stacksjs/analytics

npm i https://pkg.pr.new/@stacksjs/analytics@2573

@stacksjs/api

npm i https://pkg.pr.new/@stacksjs/api@2573

@stacksjs/arrays

npm i https://pkg.pr.new/@stacksjs/arrays@2573

@stacksjs/audio

npm i https://pkg.pr.new/@stacksjs/audio@2573

@stacksjs/auth

npm i https://pkg.pr.new/@stacksjs/auth@2573

@stacksjs/browser

npm i https://pkg.pr.new/@stacksjs/browser@2573

@stacksjs/browser-extension

npm i https://pkg.pr.new/@stacksjs/browser-extension@2573

@stacksjs/buddy

npm i https://pkg.pr.new/@stacksjs/buddy@2573

@stacksjs/build

npm i https://pkg.pr.new/@stacksjs/build@2573

@stacksjs/cache

npm i https://pkg.pr.new/@stacksjs/cache@2573

@stacksjs/calendar-api

npm i https://pkg.pr.new/@stacksjs/calendar-api@2573

@stacksjs/charts

npm i https://pkg.pr.new/@stacksjs/charts@2573

@stacksjs/chat

npm i https://pkg.pr.new/@stacksjs/chat@2573

@stacksjs/cli

npm i https://pkg.pr.new/@stacksjs/cli@2573

@stacksjs/cloud

npm i https://pkg.pr.new/@stacksjs/cloud@2573

@stacksjs/cms

npm i https://pkg.pr.new/@stacksjs/cms@2573

@stacksjs/collections

npm i https://pkg.pr.new/@stacksjs/collections@2573

@stacksjs/commerce

npm i https://pkg.pr.new/@stacksjs/commerce@2573

@stacksjs/composables

npm i https://pkg.pr.new/@stacksjs/composables@2573

@stacksjs/config

npm i https://pkg.pr.new/@stacksjs/config@2573

@stacksjs/cron

npm i https://pkg.pr.new/@stacksjs/cron@2573

@stacksjs/database

npm i https://pkg.pr.new/@stacksjs/database@2573

@stacksjs/datetime

npm i https://pkg.pr.new/@stacksjs/datetime@2573

@stacksjs/defaults

npm i https://pkg.pr.new/@stacksjs/defaults@2573

@stacksjs/desktop-build

npm i https://pkg.pr.new/@stacksjs/desktop-build@2573

@stacksjs/dns

npm i https://pkg.pr.new/@stacksjs/dns@2573

@stacksjs/docs

npm i https://pkg.pr.new/@stacksjs/docs@2573

@stacksjs/email

npm i https://pkg.pr.new/@stacksjs/email@2573

@stacksjs/enums

npm i https://pkg.pr.new/@stacksjs/enums@2573

@stacksjs/env

npm i https://pkg.pr.new/@stacksjs/env@2573

@stacksjs/error-handling

npm i https://pkg.pr.new/@stacksjs/error-handling@2573

@stacksjs/events

npm i https://pkg.pr.new/@stacksjs/events@2573

@stacksjs/faker

npm i https://pkg.pr.new/@stacksjs/faker@2573

@stacksjs/feature-flags

npm i https://pkg.pr.new/@stacksjs/feature-flags@2573

@stacksjs/features

npm i https://pkg.pr.new/@stacksjs/features@2573

@stacksjs/forms

npm i https://pkg.pr.new/@stacksjs/forms@2573

@stacksjs/git

npm i https://pkg.pr.new/@stacksjs/git@2573

@stacksjs/github

npm i https://pkg.pr.new/@stacksjs/github@2573

@stacksjs/health

npm i https://pkg.pr.new/@stacksjs/health@2573

@stacksjs/http

npm i https://pkg.pr.new/@stacksjs/http@2573

@stacksjs/i18n

npm i https://pkg.pr.new/@stacksjs/i18n@2573

@stacksjs/image

npm i https://pkg.pr.new/@stacksjs/image@2573

@stacksjs/lint

npm i https://pkg.pr.new/@stacksjs/lint@2573

@stacksjs/logging

npm i https://pkg.pr.new/@stacksjs/logging@2573

@stacksjs/mobile

npm i https://pkg.pr.new/@stacksjs/mobile@2573

@stacksjs/model-meta

npm i https://pkg.pr.new/@stacksjs/model-meta@2573

@stacksjs/newsletter

npm i https://pkg.pr.new/@stacksjs/newsletter@2573

@stacksjs/notifications

npm i https://pkg.pr.new/@stacksjs/notifications@2573

@stacksjs/objects

npm i https://pkg.pr.new/@stacksjs/objects@2573

@stacksjs/orm

npm i https://pkg.pr.new/@stacksjs/orm@2573

@stacksjs/pagination

npm i https://pkg.pr.new/@stacksjs/pagination@2573

@stacksjs/path

npm i https://pkg.pr.new/@stacksjs/path@2573

@stacksjs/payments

npm i https://pkg.pr.new/@stacksjs/payments@2573

@stacksjs/push

npm i https://pkg.pr.new/@stacksjs/push@2573

@stacksjs/query-builder

npm i https://pkg.pr.new/@stacksjs/query-builder@2573

@stacksjs/queue

npm i https://pkg.pr.new/@stacksjs/queue@2573

@stacksjs/realtime

npm i https://pkg.pr.new/@stacksjs/realtime@2573

@stacksjs/registry

npm i https://pkg.pr.new/@stacksjs/registry@2573

@stacksjs/repl

npm i https://pkg.pr.new/@stacksjs/repl@2573

@stacksjs/router

npm i https://pkg.pr.new/@stacksjs/router@2573

@stacksjs/scheduler

npm i https://pkg.pr.new/@stacksjs/scheduler@2573

@stacksjs/search-engine

npm i https://pkg.pr.new/@stacksjs/search-engine@2573

@stacksjs/security

npm i https://pkg.pr.new/@stacksjs/security@2573

@stacksjs/server

npm i https://pkg.pr.new/@stacksjs/server@2573

@stacksjs/shell

npm i https://pkg.pr.new/@stacksjs/shell@2573

@stacksjs/sites

npm i https://pkg.pr.new/@stacksjs/sites@2573

@stacksjs/skills

npm i https://pkg.pr.new/@stacksjs/skills@2573

@stacksjs/slug

npm i https://pkg.pr.new/@stacksjs/slug@2573

@stacksjs/sms

npm i https://pkg.pr.new/@stacksjs/sms@2573

@stacksjs/socials

npm i https://pkg.pr.new/@stacksjs/socials@2573

@stacksjs/storage

npm i https://pkg.pr.new/@stacksjs/storage@2573

@stacksjs/strings

npm i https://pkg.pr.new/@stacksjs/strings@2573

@stacksjs/testing

npm i https://pkg.pr.new/@stacksjs/testing@2573

@stacksjs/tinker

npm i https://pkg.pr.new/@stacksjs/tinker@2573

@stacksjs/tunnel

npm i https://pkg.pr.new/@stacksjs/tunnel@2573

@stacksjs/types

npm i https://pkg.pr.new/@stacksjs/types@2573

@stacksjs/ui

npm i https://pkg.pr.new/@stacksjs/ui@2573

@stacksjs/utils

npm i https://pkg.pr.new/@stacksjs/utils@2573

@stacksjs/validation

npm i https://pkg.pr.new/@stacksjs/validation@2573

@stacksjs/video

npm i https://pkg.pr.new/@stacksjs/video@2573

@stacksjs/whois

npm i https://pkg.pr.new/@stacksjs/whois@2573

commit: 7c259f1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

actions @stacksjs/actions core storage @stacksjs/storage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant