fix(release): never let a prerelease version fall back to the `latest` dist-tag
Closes #1233
Audited while preparing the v0.10.0 beta. Split out of #1232 because it is unrelated to that release's content — this is release infrastructure, and it should be reviewable on its own.
The failure case
.github/workflows/release.yml:338 publishes with OPENCODE_VERSION: ${{ github.ref_name }}, which for a beta tag is v0.10.0-beta.1. If OPENCODE_CHANNEL ever failed to reach that step, the fallback chain in packages/script/src/index.ts ran:
OPENCODE_CHANNELempty → skippedOPENCODE_BUMPunset → skippedOPENCODE_VERSION→0.10.0-beta.1→ does not start with0.0.0-→ returnslatest
All three npm publish calls then run --tag latest, moving the stable dist-tag onto a prerelease. Every existing user auto-upgrades to the beta on next launch — the exact outcome the beta channel exists to prevent.
This has never fired
Worth stating plainly, since the fix is precautionary:
release.yml:98and:344setOPENCODE_CHANNELto${{ contains(github.ref_name, '-') && 'beta' || 'latest' }}, which always yields one of those two strings and never empty.- There is exactly one publish step, and it carries the variable in its own
env:block. - All three
npm publishcalls inpublish.tspass--tag ${Script.channel}; none publishes bare.
So the hazard is masked entirely by one workflow expression. Nothing in the fallback itself prevents it.
Why fix it anyway
The blast radius is the whole user base, and the documented recovery — npm dist-tag add @altimateai/altimate-code@<good> latest — needs npm publish credentials that most of the team does not hold. Detection you cannot act on is not a safety net. The release process makes "confirm latest did not move" a mandatory post-publish assertion precisely because this class of mistake is unrecoverable for most people who would hit it.
The change
Extracted the decision into packages/script/src/channel.ts as a pure resolveChannel. It could not be tested in place: importing script/src/index.ts checks the bun version, shells out to git branch, and fetches the npm registry at module load.
Behaviour:
OPENCODE_VERSION | Before | After |
|---|---|---|
v0.10.0-beta.1 | latest | beta |
v0.10.0 | latest | latest |
0.0.0-<branch>-<ts> | falls through to branch channel | unchanged |
An explicit OPENCODE_CHANNEL still wins outright, so the normal workflow path is untouched.
Tests
Six cases in packages/opencode/test/release/channel.test.ts covering both directions plus the preview path. Mutation-verified: restoring the old return "latest" fails exactly the prerelease regression test and nothing else.
Typecheck clean across 13 packages; marker guard passes.
🤖 Generated with Claude Code
Summary by cubic
Prevents prerelease versions from ever landing on the latest npm dist-tag. Previously, if OPENCODE_CHANNEL failed to reach the publish step, a beta tag like v0.10.0-beta.1 would fall through to latest, auto-upgrading every existing user on next launch.
This hasn't fired yet — the workflow always sets OPENCODE_CHANNEL — but recovery (npm dist-tag add ...) requires publish credentials most of the team doesn't hold, so the fallback alone isn't a safety net.
Changes
- Extracts the dist-tag decision into
resolveChannelinpackages/script/src/channel.tsas a pure function so it can be tested without importing the module. - Prerelease versions now resolve to
beta;0.0.0-preview builds still fall through to the branch channel; stable versions still resolve tolatest. - An explicit
OPENCODE_CHANNELstill wins, so the normal workflow path is unchanged. - Adds tests in
packages/opencode/test/release/channel.test.tscovering both directions plus the preview path. - Closes #1233.
Summary by CodeRabbit
-
Improvements
- Release publishing now selects the appropriate distribution channel more reliably:
- Explicit channel settings take priority.
- Prerelease versions are published to the beta channel.
- Stable versions use the latest channel.
- Preview builds defer to the branch-based channel.
- Explicit bump commands continue to use the latest channel.
- Release publishing now selects the appropriate distribution channel more reliably:
-
Tests
- Added coverage for release-channel selection across supported version, preview, and configuration scenarios.