From 264f7aa9220bae020881519edb004d6eccd90908 Mon Sep 17 00:00:00 2001 From: Nikolas Demiridis Date: Wed, 9 Sep 2026 14:42:20 +0300 Subject: [PATCH 1/2] Install with a frozen lockfile in every workflow `bun install` treats a lockfile that disagrees with a `package.json` as something to silently repair: it exits 0, rewrites `bun.lock` in the runner, and the job proceeds against a dependency graph nobody committed. Nothing then reports the drift, because from the outside a resolved install and a locked install look identical. They were not identical. `bun.lock` recorded the extension at 0.4.1 and a peer range of `>=17.0.7 <18` while `packages/extension/package.json` said 0.6.0 and `<19`, through an unknown number of green pipelines, because no job ever asked. It was noticed only when a plain `bun install` in a fresh worktree rewrote the file as a side effect of running the gate. Measured both ways against the same introduced drift, on this tree: `bun install` exits 0 and modifies `bun.lock`; `bun install --frozen-lockfile` exits 1 with "lockfile had changes, but lockfile is frozen" and names the fix. So the flag is a real check and not a no-op, which is the only reason it is worth adding. Applied to all nine install sites -- the six in ci.yml, plus release.yml, auto-commit-dist.yml and refresh-bundle.yml. The last two build the committed bundle, and freezing them is the point rather than an oversight: a bundle built from a dependency graph the lockfile does not describe is the artifact this rule exists to prevent, and blocking that build until the lockfile is committed is the correct outcome. The manual command in refresh-bundle.yml's header comment is left alone; it is a local invocation, not a pipeline. CONTRIBUTING.md says what to do when it fails, which is the same thing bun's own error says: run `bun install` locally and commit `bun.lock` alongside the dependency change that moved it. --- .github/workflows/auto-commit-dist.yml | 2 +- .github/workflows/ci.yml | 25 +++++++++++++++++++------ .github/workflows/refresh-bundle.yml | 2 +- .github/workflows/release.yml | 2 +- CONTRIBUTING.md | 8 ++++++++ 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/.github/workflows/auto-commit-dist.yml b/.github/workflows/auto-commit-dist.yml index 06eb9d9f..061893ad 100644 --- a/.github/workflows/auto-commit-dist.yml +++ b/.github/workflows/auto-commit-dist.yml @@ -30,7 +30,7 @@ jobs: bun-version: 1.3.14 - name: Install JS workspace dependencies - run: bun install + run: bun install --frozen-lockfile - name: Rebuild the extension bundle run: bun run build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bfc2e8b0..533f600c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,8 +110,21 @@ jobs: with: bun-version: 1.3.14 + # `--frozen-lockfile` on every install in this repository's workflows. + # Without it, `bun install` treats a lockfile that disagrees with a + # `package.json` as something to silently rewrite: it exits 0, updates + # `bun.lock` in the runner, and the job proceeds against a dependency + # graph nobody committed. That is not hypothetical -- `bun.lock` + # recorded the extension at 0.4.1 and a peer range of `<18` while + # `package.json` said 0.6.0 and `<19`, through an unknown number of + # green pipelines, because no job ever asked. Measured both ways on + # the same drift: plain `bun install` exits 0 and rewrites the file; + # `--frozen-lockfile` exits 1 and names the fix. + # + # When this fails, the lockfile is genuinely out of date: run + # `bun install` locally and commit the resulting `bun.lock`. - name: Install JS workspace dependencies - run: bun install + run: bun install --frozen-lockfile # Biome formats the JS/TS half. `cargo fmt` above covers Rust only, # so without this a TypeScript formatting regression reaches main @@ -170,7 +183,7 @@ jobs: bun-version: 1.3.14 - name: Install JS workspace dependencies - run: bun install + run: bun install --frozen-lockfile - name: Run Rust tests run: cargo test --workspace @@ -188,7 +201,7 @@ jobs: bun-version: 1.3.14 - name: Install JS workspace dependencies - run: bun install + run: bun install --frozen-lockfile # `strict: true` was declared from day one but never enforced: no local # script and no CI job ran `tsc`, so real compile errors shipped in @@ -206,7 +219,7 @@ jobs: bun-version: 1.3.14 - name: Install JS workspace dependencies - run: bun install + run: bun install --frozen-lockfile - name: Check generated code is up to date run: bun run generate --check @@ -221,7 +234,7 @@ jobs: bun-version: 1.3.14 - name: Install JS workspace dependencies - run: bun install + run: bun install --frozen-lockfile - name: Fail if the bundle is not a tracked file run: git ls-files --error-unmatch packages/extension/dist/index.js @@ -254,7 +267,7 @@ jobs: bun-version: 1.3.14 - name: Install JS workspace dependencies - run: bun install + run: bun install --frozen-lockfile - name: Run dependency audit run: | diff --git a/.github/workflows/refresh-bundle.yml b/.github/workflows/refresh-bundle.yml index cd5e2048..d74d2fc8 100644 --- a/.github/workflows/refresh-bundle.yml +++ b/.github/workflows/refresh-bundle.yml @@ -25,7 +25,7 @@ jobs: bun-version: 1.3.14 - name: Install JS workspace dependencies - run: bun install + run: bun install --frozen-lockfile - name: Rebuild the extension bundle run: bun run build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 92534bef..3146674d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -152,7 +152,7 @@ jobs: bun-version: 1.3.14 - name: Install JS workspace dependencies - run: bun install + run: bun install --frozen-lockfile - name: Run conformance unit tests run: bun test tests/conformance diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a921e6a..e7fa28cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -231,6 +231,14 @@ what makes them evidence, and `assets/`, where the logo's parts carry labels of `release/live-conformance/*.json` is exempt for the same reason as `fixtures/` — those reports are harness output copied verbatim — while that directory's README, being authored prose, is not. +### The lockfile is checked, not repaired, by CI + +Every workflow installs with `bun install --frozen-lockfile`. If `bun.lock` disagrees with any +`package.json`, the job fails and says so, rather than quietly resolving a different dependency +graph and continuing — which is what a plain `bun install` does, exiting 0 and rewriting the +lockfile in the runner where nobody sees it. Run `bun install` locally and commit the updated +`bun.lock` in the same change that touched the dependency. + ### Commit messages carry technical content only No tool attribution, no agent names, no session links. A trailer of that kind says nothing about the From 11e1e0bd9c7e9a48eed89204c38778ad45c1bf1f Mon Sep 17 00:00:00 2001 From: Nikolas Demiridis Date: Wed, 9 Sep 2026 17:51:21 +0300 Subject: [PATCH 2/2] Freeze the documented docker equivalent of the bundle refresh too The previous commit left this one plain and justified it as "a local invocation, not a pipeline". That justification was wrong. The command is the documented *substitute* for the refresh-bundle workflow, for use before that workflow exists on the default branch, and its output is the committed extension bundle -- the same artifact, reached by a different route. A bundle built from a dependency graph the lockfile does not describe is what this rule exists to prevent, and it does not stop being that because a human typed the command. The discriminator is whether the output is committed or verified, not whether a human or a runner starts it. By that test `scripts/setup.sh` stays plain and deliberately so: it bootstraps a developer's own environment, commits nothing, and is the normal path by which a lockfile gets legitimately updated after a dependency change. Freezing it would block the repair the other nine sites tell you to perform. Found by a reviewer's wider grep. Mine was scoped to `run:` lines under .github/, which is the same narrowing this repository keeps relearning: the scan reported "none" because of where it was pointed, not because there was nothing there. --- .github/workflows/refresh-bundle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-bundle.yml b/.github/workflows/refresh-bundle.yml index d74d2fc8..f2b21850 100644 --- a/.github/workflows/refresh-bundle.yml +++ b/.github/workflows/refresh-bundle.yml @@ -7,7 +7,7 @@ # refresh the bundle. Until this workflow exists on the default branch # (dispatch requires that), the equivalent is: # docker run --platform linux/amd64 -v $REPO:/src -w /src oven/bun:1.3.14 \ -# sh -c "bun install && bun run build" +# sh -c "bun install --frozen-lockfile && bun run build" # on a source copy. See the bundle-check job in ci.yml. name: Refresh Committed Bundle (manual)