Problem
If OPENCODE_CHANNEL is ever empty when publish.ts runs, a prerelease tag publishes to the latest dist-tag and auto-upgrades every existing stable user onto a beta.
The channel fallback in packages/script/src/index.ts:25-30 is not prerelease-aware:
const CHANNEL = await (async () => {
if (env.OPENCODE_CHANNEL) return env.OPENCODE_CHANNEL
if (env.OPENCODE_BUMP) return "latest"
if (env.OPENCODE_VERSION && !env.OPENCODE_VERSION.replace(/^v/, "").startsWith("0.0.0-")) return "latest"
return await $`git branch --show-current`.text().then((x) => x.trim())
})()
Failure case
.github/workflows/release.yml:338 publishes with OPENCODE_VERSION: ${{ github.ref_name }}. For a beta tag that is v0.10.0-beta.1. If OPENCODE_CHANNEL did not reach the step:
env.OPENCODE_CHANNEL is empty → skipped
OPENCODE_BUMP unset → skipped
OPENCODE_VERSION = v0.10.0-beta.1 → .replace(/^v/, "") → 0.10.0-beta.1 → does not start with 0.0.0- → returns latest
All three npm publish calls then run --tag latest, and the latest dist-tag moves to a prerelease. Every existing user auto-upgrades to the beta on next launch — the exact outcome the beta channel exists to prevent.
Why it has not happened
release.yml:98 and :344 set OPENCODE_CHANNEL to ${{ contains(github.ref_name, '-') && 'beta' || 'latest' }}, which always evaluates to one of those two strings and never to empty. Verified: 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 today the hazard is masked entirely by one workflow expression. Nothing in the fallback itself prevents it.
Why it is worth fixing anyway
The blast radius is the whole user base, and the documented recovery (npm dist-tag add @altimateai/altimate-code@<good> latest) requires npm publish credentials that most of the team does not hold — so detection without the ability to remediate is thin protection. 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.
Defence in depth: make the fallback refuse to route a prerelease version to latest, so the safety does not rest solely on an env var reaching a step.
Fix
In the OPENCODE_VERSION branch, treat a version carrying a semver prerelease component as beta rather than latest. The 0.0.0- preview path must keep falling through to the branch-name channel, so the guard is scoped to the branch that already excludes it.
Problem
If
OPENCODE_CHANNELis ever empty whenpublish.tsruns, a prerelease tag publishes to thelatestdist-tag and auto-upgrades every existing stable user onto a beta.The channel fallback in
packages/script/src/index.ts:25-30is not prerelease-aware:Failure case
.github/workflows/release.yml:338publishes withOPENCODE_VERSION: ${{ github.ref_name }}. For a beta tag that isv0.10.0-beta.1. IfOPENCODE_CHANNELdid not reach the step:env.OPENCODE_CHANNELis empty → skippedOPENCODE_BUMPunset → skippedOPENCODE_VERSION=v0.10.0-beta.1→.replace(/^v/, "")→0.10.0-beta.1→ does not start with0.0.0-→ returnslatestAll three
npm publishcalls then run--tag latest, and thelatestdist-tag moves to a prerelease. Every existing user auto-upgrades to the beta on next launch — the exact outcome the beta channel exists to prevent.Why it has not happened
release.yml:98and:344setOPENCODE_CHANNELto${{ contains(github.ref_name, '-') && 'beta' || 'latest' }}, which always evaluates to one of those two strings and never to empty. Verified: there is exactly one publish step, and it carries the variable in its ownenv:block. All threenpm publishcalls inpublish.tspass--tag ${Script.channel}; none publishes bare.So today the hazard is masked entirely by one workflow expression. Nothing in the fallback itself prevents it.
Why it is worth fixing anyway
The blast radius is the whole user base, and the documented recovery (
npm dist-tag add @altimateai/altimate-code@<good> latest) requires npm publish credentials that most of the team does not hold — so detection without the ability to remediate is thin protection. The release process makes "confirmlatestdid not move" a mandatory post-publish assertion precisely because this class of mistake is unrecoverable for most people who would hit it.Defence in depth: make the fallback refuse to route a prerelease version to
latest, so the safety does not rest solely on an env var reaching a step.Fix
In the
OPENCODE_VERSIONbranch, treat a version carrying a semver prerelease component asbetarather thanlatest. The0.0.0-preview path must keep falling through to the branch-name channel, so the guard is scoped to the branch that already excludes it.