What
The restored gate (#916, b214ea6fd) is good and it works — see the reinstatement proof at the bottom. This is one ordering residual in the chain around it.
package.json:56:
"publish-all": "bun run format && bun run rebuild && bun run require-green-ci && bun run bunset && bun run publish-login && bun run publish-workspaces",
format is oxlint --fix && oxfmt "{packages,providers,examples}/*/src/**/*.{js,ts,tsx,json}" — it writes to source. rebuild regenerates dist. Both run before the gate. require-green-ci then resolves git rev-parse HEAD and asks GitHub about that SHA:
$ sed -n '44,47p' scripts/require-green-ci.ts
const head = spawnSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" });
if (head.status !== 0) fail(`Could not read HEAD: ${head.stderr.trim()}`);
const sha = head.stdout.trim();
So the run it checks covers the commit as it was before format touched it, while publish-workspaces packs the working tree. The script knows:
$ sed -n '84,93p' scripts/require-green-ci.ts
const dirty = spawnSync("git", ["status", "--porcelain"], { encoding: "utf8" });
if (dirty.status === 0 && dirty.stdout.trim() !== "") {
// Not fatal — `publish-all` runs `format` and `rebuild` ahead of this, so a
// dirty tree is expected. But the run above tested the commit, not the tree
// about to be packed, and that difference is worth naming.
console.warn(
`⚠ Working tree is not clean; the run above tested ${sha.slice(0, 7)}, not what is on disk.`
);
}
A console.warn inside a release script that runs unattended and is followed immediately by bunset (commit = true, tag = true, push = true, release = true in .bunset.toml) is not a control. It is the same shape as the comment-counts that #906 replaced with a ratchet.
Why it matters
The gate's own docstring states the principle it is then arranged to violate:
"the commit is what consumers install, not the working tree"
Under the current ordering the thing checked and the thing published are, by construction, two different trees whenever format has anything to do — which is exactly the case an unattended release cannot distinguish from the clean one. The severity is bounded (oxfmt is whitespace, oxlint --fix is autofixes) but the guarantee is unbounded in the wrong direction: oxlint --fix applies rule autofixes to source, and nothing re-runs a test after it.
This is not the 0.4.7 failure returning. It is the one seam the replacement left, and it costs two characters to close.
Proposed fix
Pick one:
- Reorder —
bun run require-green-ci && bun run format && bun run rebuild && bun run bunset && …. The gate then reads the commit while it still is the commit. format before the gate buys nothing anyway: format-check has run in CI since 509724031 (.github/workflows/test.yml:95-96), so a commit that is green is already formatted, and format at publish time is a no-op on exactly the commits the gate lets through.
- Or make the dirty-tree check fatal rather than a warning, with the same
WORKGLOW_SKIP_CI_GATE=1 escape the rest of the script uses.
Option 1 is strictly better: it removes the divergence instead of refusing to publish through it, and it makes format at publish time redundant, which is the honest reading of what CI now covers.
Extend PublishGate.test.ts with the ordering assertion it already has the shape for — it asserts require-green-ci comes before bunset; add that it comes before format.
The gate itself is verified — reinstated and measured
$ python3 -c "...replace 'bun run rebuild && bun run require-green-ci && bun run bunset'
with 'bun run rebuild && bun run bunset' in package.json"
$ bunx vitest run packages/test/src/test/util/PublishGate.test.ts
FAIL src/test/util/PublishGate.test.ts > the release gate on publish-all > checks the commit's CI run before it publishes
AssertionError: expected 'bun run format && bun run rebuild && …' to contain 'require-green-ci'
FAIL src/test/util/PublishGate.test.ts > the release gate on publish-all > gates before it bumps, not after
Test Files 1 failed (1)
The ratchet fails when the defect it was written for is reinstated. #902 is closed for the right reason.
Measured on origin/main @ 2d36880 (0.6.0). Found during the 2026-09-14 review of packages/.
What
The restored gate (#916,
b214ea6fd) is good and it works — see the reinstatement proof at the bottom. This is one ordering residual in the chain around it.package.json:56:formatisoxlint --fix && oxfmt "{packages,providers,examples}/*/src/**/*.{js,ts,tsx,json}"— it writes to source.rebuildregeneratesdist. Both run before the gate.require-green-cithen resolvesgit rev-parse HEADand asks GitHub about that SHA:So the run it checks covers the commit as it was before
formattouched it, whilepublish-workspacespacks the working tree. The script knows:A
console.warninside a release script that runs unattended and is followed immediately bybunset(commit = true,tag = true,push = true,release = truein.bunset.toml) is not a control. It is the same shape as the comment-counts that #906 replaced with a ratchet.Why it matters
The gate's own docstring states the principle it is then arranged to violate:
Under the current ordering the thing checked and the thing published are, by construction, two different trees whenever
formathas anything to do — which is exactly the case an unattended release cannot distinguish from the clean one. The severity is bounded (oxfmtis whitespace,oxlint --fixis autofixes) but the guarantee is unbounded in the wrong direction:oxlint --fixapplies rule autofixes to source, and nothing re-runs a test after it.This is not the 0.4.7 failure returning. It is the one seam the replacement left, and it costs two characters to close.
Proposed fix
Pick one:
bun run require-green-ci && bun run format && bun run rebuild && bun run bunset && …. The gate then reads the commit while it still is the commit.formatbefore the gate buys nothing anyway:format-checkhas run in CI since509724031(.github/workflows/test.yml:95-96), so a commit that is green is already formatted, andformatat publish time is a no-op on exactly the commits the gate lets through.WORKGLOW_SKIP_CI_GATE=1escape the rest of the script uses.Option 1 is strictly better: it removes the divergence instead of refusing to publish through it, and it makes
formatat publish time redundant, which is the honest reading of what CI now covers.Extend
PublishGate.test.tswith the ordering assertion it already has the shape for — it assertsrequire-green-cicomes beforebunset; add that it comes beforeformat.The gate itself is verified — reinstated and measured
The ratchet fails when the defect it was written for is reinstated. #902 is closed for the right reason.
Measured on
origin/main@2d36880(0.6.0). Found during the 2026-09-14 review ofpackages/.