Summary
Version bumps in workspace manifests (Cargo.toml, package.json, npm/* platform manifests) currently land in feature commits whenever a breaking change is introduced. This breaks main: the bumped version's prebuilt platform binaries (@momics/iroh-http-node-darwin-arm64@X.Y.Z etc.) are only published from CI on tag, so for the entire window between "feature commit lands on main" and "next chore: release tag is cut", npm ci cannot resolve iroh-http-node's optionalDependencies and crashes on every platform.
The two known occurrences:
195a99d (feat(api)!: rename node.connect→dial …) — bumped 0.3.4 → 0.4.0 inside a feature commit. Caught by the docs(adr-014,ffi): record D4 resolution CI run, fixed by 7f7c0c0 (revert to 0.3.4).
- The same dep-range widening (
@momics/iroh-http-shared ^0.3 → ^0.4) was implicit in that bump and would have broken downstream consumers.
The intended workflow is: only scripts/release.sh bumps versions, and it does so immediately before tagging + publishing, so the bump and the binary upload land together. There is currently nothing enforcing this — only convention.
Evidence
- scripts/version.sh is comprehensive (covers
Cargo.toml, package.json, npm/*/package.json, deno.jsonc, deno.json, adapter.ts VERSION constant, dep ranges, both Cargo.locks, package-lock.json, deno.lock) but is only a tool — it doesn't restrict who or when calls it.
7f7c0c0 (revert) commit message documents the failure mode in detail.
- CI failure log on run
25207770562 shows the exact crash: npm error Missing: @momics/iroh-http-node-darwin-arm64@ from lock file (5 platforms) plus TypeError: Invalid Version: '' from semver when the lockfile has bare {"optional": true} stubs without a version field.
Impact
- Main breaks silently for the duration of every cross-release breaking change. Two occurrences in two months.
- Every contributor is one careless
cargo set-version away from breaking CI for everyone else.
- Release surprise: a bump mid-release-cycle widens dep ranges (
^0.3 → ^0.4) implicitly, which can re-resolve transitive deps in unexpected ways the next time anyone runs npm install.
- Loss of trust in CI green — when CI breaks for "release plumbing" reasons unrelated to the change under review, contributors learn to ignore CI failures, which is the worst possible outcome.
Remediation
A small, mechanical CI gate plus a docs note. ~30 minutes of work.
1. Add a CI job version-bump-policy that fails when a non-release commit modifies any version-bearing field. Pseudocode:
# In .github/workflows/ci.yml, run on PR and on push to main.
git fetch origin main
CHANGED=$(git diff --name-only origin/main...HEAD)
VERSION_FILES=$(echo "$CHANGED" | grep -E '(Cargo\.toml|package\.json|deno\.json|deno\.jsonc|VERSION)$' || true)
if [ -n "$VERSION_FILES" ]; then
# Verify every commit that touches a version file is "chore: release"
for sha in $(git log origin/main..HEAD --format=%H -- $VERSION_FILES); do
subject=$(git log -1 --format=%s "$sha")
case "$subject" in
"chore: release "*|"chore(release): "*) ;;
*) echo "Commit $sha touches version file but is not 'chore: release': $subject"; exit 1 ;;
esac
done
fi
This catches the 195a99d failure mode at PR time, before merge.
2. Tighten scripts/version.sh with a single early check that fails if it is invoked from a dirty tree or a non-release working state:
# At the top of version.sh, after argument parsing:
if [ -n "$(git status --porcelain)" ] && [ -z "${ALLOW_DIRTY:-}" ]; then
echo "Error: working tree is dirty. version.sh should only run from a clean tree as part of scripts/release.sh." >&2
echo "Set ALLOW_DIRTY=1 to override (testing only)." >&2
exit 1
fi
3. Document the policy in CONTRIBUTING.md (or wherever release process is described): "Do not edit version fields manually. Use scripts/release.sh."
Acceptance criteria
- CI workflow contains a
version-bump-policy job (or step) that fails when a commit on the PR touches a version field with a non-chore: release subject.
- The job is required for merge.
scripts/version.sh refuses to run on a dirty tree without ALLOW_DIRTY=1.
CONTRIBUTING.md (or docs/build-and-test.md) explicitly documents that manual version edits are prohibited.
- A test PR that bumps
Cargo.toml version outside a chore: release commit fails CI; a chore: release commit passes.
Out of scope
- Changing the actual release process (still:
scripts/release.sh then tag then CI publishes).
- The
optionalDependencies model itself (napi-rs prebuild pattern is fine).
- Auto-generating release commits — humans still cut releases.
References
7f7c0c0 revert(release): roll workspace back to 0.3.4 to unbreak CI
195a99d feat(api)!: rename node.connect→dial — original offending bump
- scripts/version.sh — current bump tool
- scripts/release.sh — intended caller
Summary
Version bumps in workspace manifests (
Cargo.toml,package.json, npm/* platform manifests) currently land in feature commits whenever a breaking change is introduced. This breaks main: the bumped version's prebuilt platform binaries (@momics/iroh-http-node-darwin-arm64@X.Y.Zetc.) are only published from CI on tag, so for the entire window between "feature commit lands on main" and "nextchore: releasetag is cut",npm cicannot resolveiroh-http-node'soptionalDependenciesand crashes on every platform.The two known occurrences:
195a99d(feat(api)!: rename node.connect→dial …) — bumped 0.3.4 → 0.4.0 inside a feature commit. Caught by thedocs(adr-014,ffi): record D4 resolutionCI run, fixed by7f7c0c0(revert to 0.3.4).@momics/iroh-http-shared^0.3→^0.4) was implicit in that bump and would have broken downstream consumers.The intended workflow is: only scripts/release.sh bumps versions, and it does so immediately before tagging + publishing, so the bump and the binary upload land together. There is currently nothing enforcing this — only convention.
Evidence
Cargo.toml,package.json,npm/*/package.json,deno.jsonc,deno.json,adapter.tsVERSIONconstant, dep ranges, bothCargo.locks,package-lock.json,deno.lock) but is only a tool — it doesn't restrict who or when calls it.7f7c0c0(revert) commit message documents the failure mode in detail.25207770562shows the exact crash:npm error Missing: @momics/iroh-http-node-darwin-arm64@ from lock file(5 platforms) plusTypeError: Invalid Version: ''from semver when the lockfile has bare{"optional": true}stubs without a version field.Impact
cargo set-versionaway from breaking CI for everyone else.^0.3→^0.4) implicitly, which can re-resolve transitive deps in unexpected ways the next time anyone runsnpm install.Remediation
A small, mechanical CI gate plus a docs note. ~30 minutes of work.
1. Add a CI job
version-bump-policythat fails when a non-release commit modifies any version-bearing field. Pseudocode:This catches the
195a99dfailure mode at PR time, before merge.2. Tighten
scripts/version.shwith a single early check that fails if it is invoked from a dirty tree or a non-release working state:3. Document the policy in
CONTRIBUTING.md(or wherever release process is described): "Do not edit version fields manually. Usescripts/release.sh."Acceptance criteria
version-bump-policyjob (or step) that fails when a commit on the PR touches a version field with a non-chore: releasesubject.scripts/version.shrefuses to run on a dirty tree withoutALLOW_DIRTY=1.CONTRIBUTING.md(ordocs/build-and-test.md) explicitly documents that manual version edits are prohibited.Cargo.tomlversionoutside achore: releasecommit fails CI; achore: releasecommit passes.Out of scope
scripts/release.shthen tag then CI publishes).optionalDependenciesmodel itself (napi-rs prebuild pattern is fine).References
7f7c0c0revert(release): roll workspace back to 0.3.4 to unbreak CI195a99dfeat(api)!: rename node.connect→dial — original offending bump