fix(release): never let a prerelease version fall back to the `latest` dist-tag

SA
2mo ago
+88-3

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:

  1. OPENCODE_CHANNEL empty → skipped
  2. OPENCODE_BUMP unset → skipped
  3. OPENCODE_VERSION → 0.10.0-beta.1 → does not start with 0.0.0- → returns latest

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:98 and :344 set OPENCODE_CHANNEL to ${{ 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 publish calls in publish.ts pass --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_VERSIONBeforeAfter
v0.10.0-beta.1latestbeta
v0.10.0latestlatest
0.0.0-<branch>-<ts>falls through to branch channelunchanged

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 resolveChannel in packages/script/src/channel.ts as 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 to latest.
  • An explicit OPENCODE_CHANNEL still wins, so the normal workflow path is unchanged.
  • Adds tests in packages/opencode/test/release/channel.test.ts covering 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.
  • Tests

    • Added coverage for release-channel selection across supported version, preview, and configuration scenarios.