Background
The release workflow already publishes prereleases correctly (release.yml:75-78 flags any tag containing - as a GitHub prerelease), and GitHub's /releases/latest endpoint excludes prereleases by design. As a result, looper upgrade on the default (stable) channel will never auto-upgrade users to a prerelease — which is the desired behavior for stable.
However, there is currently no way to opt into prereleases from the CLI. The spec at specs/2026-04-22-install-upgrade-strategy/spec.md:768-780 already describes a --channel beta flag, but the code has no channel concept (channel only appears as a metadata field today).
This issue tracks adding first-class beta channel support so internal users and early adopters can dogfood prereleases without breaking the stable auto-upgrade contract.
Goals
- Let users opt into prereleases via an explicit, persistent channel setting.
- Keep the stable channel completely unaffected — daily auto-upgrade must never pull a prerelease for stable users.
- Make the channel visible in
looper status / version output so users always know which track they are on.
Non-goals
- Multiple parallel beta channels (e.g.
alpha, nightly). Start with a single beta track.
- Downgrade support. Switching from beta back to stable does not roll back the binary; it just stops pulling future prereleases until stable catches up.
Proposed design
CLI surface
- New flag on
looper upgrade and looper daemon install: --channel {stable,beta} (default stable).
- Persisted preference in
~/.looper/config.json under a new upgrade.channel key so the daily auto-upgrade respects the choice without re-passing the flag.
looper status and --version --json include the active channel.
Resolver changes (internal/cliapp/upgrade.go, daemon_install.go)
fetchReleaseMetadata(ctx, tag) stays as-is for explicit tags.
- Add
fetchLatestRelease(ctx, channel):
stable → GET /repos/{owner}/{repo}/releases/latest (current behavior).
beta → GET /repos/{owner}/{repo}/releases?per_page=30, filter out drafts, then pick the highest semver among entries where prerelease == true || prerelease == false. (Beta users should also receive stable releases when stable > latest beta.)
isSemverUpgradeAvailable already orders prereleases correctly (semver_test.go:17-22), so no changes needed there.
Auto-upgrade behavior
- Daily auto-upgrade reads
upgrade.channel from config; defaults to stable.
- Beta users get prereleases automatically once opted in. Stable users remain protected.
Telemetry / logging
- Log the resolved channel and selected tag in upgrade output (both human and
--json) so support can quickly tell which track a machine is on.
Acceptance criteria
References
specs/2026-04-22-install-upgrade-strategy/spec.md:768-780 — original prerelease/channel spec
specs/2026-04-22-install-upgrade-strategy/checklist.md:142,178 — open checklist items for prerelease channel
internal/cliapp/upgrade.go:500-530 — current fetchLatestCLIVersion / fetchLatestDaemonRelease
internal/cliapp/semver.go:92-151 — prerelease-aware semver comparison (already correct)
.github/workflows/release.yml:75-84 — prerelease tag detection in release pipeline
Background
The release workflow already publishes prereleases correctly (
release.yml:75-78flags any tag containing-as a GitHub prerelease), and GitHub's/releases/latestendpoint excludes prereleases by design. As a result,looper upgradeon the default (stable) channel will never auto-upgrade users to a prerelease — which is the desired behavior for stable.However, there is currently no way to opt into prereleases from the CLI. The spec at
specs/2026-04-22-install-upgrade-strategy/spec.md:768-780already describes a--channel betaflag, but the code has no channel concept (channelonly appears as a metadata field today).This issue tracks adding first-class beta channel support so internal users and early adopters can dogfood prereleases without breaking the stable auto-upgrade contract.
Goals
looper status/ version output so users always know which track they are on.Non-goals
alpha,nightly). Start with a singlebetatrack.Proposed design
CLI surface
looper upgradeandlooper daemon install:--channel {stable,beta}(defaultstable).~/.looper/config.jsonunder a newupgrade.channelkey so the daily auto-upgrade respects the choice without re-passing the flag.looper statusand--version --jsoninclude the active channel.Resolver changes (
internal/cliapp/upgrade.go,daemon_install.go)fetchReleaseMetadata(ctx, tag)stays as-is for explicit tags.fetchLatestRelease(ctx, channel):stable→GET /repos/{owner}/{repo}/releases/latest(current behavior).beta→GET /repos/{owner}/{repo}/releases?per_page=30, filter out drafts, then pick the highest semver among entries whereprerelease == true || prerelease == false. (Beta users should also receive stable releases when stable > latest beta.)isSemverUpgradeAvailablealready orders prereleases correctly (semver_test.go:17-22), so no changes needed there.Auto-upgrade behavior
upgrade.channelfrom config; defaults tostable.Telemetry / logging
--json) so support can quickly tell which track a machine is on.Acceptance criteria
looper upgrade --channel betapulls the latest prerelease when one is newer than the installed version.looper upgrade --channel beta --check --jsonreports the prerelease tag without installing.looper upgrade(no flag, default config) never selects a prerelease, even if one is newer.~/.looper/config.json.looper statusshows the active channel.References
specs/2026-04-22-install-upgrade-strategy/spec.md:768-780— original prerelease/channel specspecs/2026-04-22-install-upgrade-strategy/checklist.md:142,178— open checklist items for prerelease channelinternal/cliapp/upgrade.go:500-530— currentfetchLatestCLIVersion/fetchLatestDaemonReleaseinternal/cliapp/semver.go:92-151— prerelease-aware semver comparison (already correct).github/workflows/release.yml:75-84— prerelease tag detection in release pipeline