nightly-next: sign once as cmux NIGHTLY and publish to the one feed
Leo, 2026-11-05: "JUST POINT ME TO THE ONE APP/THE ONE FEED", "THERE SHOULDN'T BE 2"; and "if we're only doing 1, just do the best app version to stick with". The app the team runs is cmux NIGHTLY (com.cmuxterm.app.nightly), with evidence in cmuxterm-hq#7156. So nightly-next now signs each build once, as cmux NIGHTLY, and publishes it to the feed that app polls. The cmux NEXT-identity build, its feed writes, and the second signing are gone. Carries out #33050.
One signing
decide's nightly-next identity becomes cmux NIGHTLY:com.cmuxterm.app.nightly,cmux-nightlyandcmux.nightly.entitlements. That means the NIGHTLY Developer ID profile, keychain group7WLXT3NR37.com.cmuxterm.app.nightlyand passkeys.sign_channelisnightlyandprofile_channelisNIGHTLY. This is the same path the NIGHTLY-identity notary probe already takes. The embed step refuses a profile for any other App ID.- The cmux NEXT sign-in scheme override (
LSEnvironment:CMUX_AUTH_CALLBACK_SCHEME) is dropped. cmux NIGHTLY takes its own default. - Removed: the five copy jobs (
legacy-identity-target,sign-,verify-,publish-nightly-identity-legacy,nightly-identity-legacy-published) and the 17 in-job copy steps. The sign job's budget goes from 320 back to 160 minutes.
One feed
NIGHTLY_NEXT_FEED_BASEishttps://files-next.cmux.com/nightly-next(R2 prefixnightly-next/).publish-nightly's compare-and-swap write, guard, ring tags, read-back and notes now all go to that feed.- The release push uses
feed=nightly-next-legacy, the id the app'sReleasePushListenergives that feed. - The rolling DMG name is now moved by
publish-nightly-next-github-names, only while the build is the feed's newest. - The build-number checks read that feed plus classic NIGHTLY.
- Canary verdicts and
pulled.jsonstay innightly-next/next/, where the app reads them (UpdaterService.channelFolder). - verdict, rollback and canary follow the one feed. verdict no longer retags the retired feed.
- The feed keeps the Nightly build before the newest (
nightly_next_ring.py legacy, in both ring modes), so a launch crash has a build to roll back to. Today's live feed holds a single item, which leaves the launch smoke'sdecide()with nothing to serve. With the ring off, an unreadable live feed now warns and publishes the new build instead of stopping.
Handover for a stray cmux NEXT install
- Sparkle never installs a different bundle id. The app has no informational-update support, and a higher item with no enclosure would go down its normal install path.
- So the retired feed gets no appcast item. It gets a signed announcement card,
release-notes/next/retired-feed-announcements.json, signed with the content key and written asnightly-next/next/notes/announcements.json. That is the one feed-side message the app shows. It is text only and points to cmux.com/next.
Not touched
files.cmux.com/nightlyand every classic-frozen path.- The nightly runner routing.
The scheduled watchers that run from main move in #36228, which lands first.
Follow-ups
- Delete the copy's now-unused scripts and their unit tests.
- stable-next: its feed has never published; it reads the retired feed and expects a cmux NEXT DMG.
- The dev measurement tools that install "cmux NEXT.app".
Tests
- Rebased onto feat-cmux-next
4a2ed94e(#35918). Its signer-only routes are unchanged on every remaining job, and the signing-route testtest_no_signing_job_ever_resolves_to_a_shared_labelpasses. - Every nightly, signing and routing test passes except
test_nightly_next_arm64_only.py,test_ci_upload_artifact_retention.py,test_short_timeout_guard.pyandtest_ci_signing_keychain_is_deleted_before_bundle_code.py. These four fail the same way on base4a2ed94e. - Rewritten for the new contract:
test_nightly_next_identity.py,test_nightly_next_post_publish.pyandtest_nightly_next_rolling_dmg_identity.py. - New:
test_a_crash_rolls_back_on_the_one_feeds_shape(launch smokedecide()on a two-item cmux-next feed) and the ring tests for keeping the build before the newest. - Checks on the removed jobs were dropped from the routing, coverage, runner and universal-build tests.
Agent session: agent session
🤖 Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Signs every nightly-next build once, as cmux NIGHTLY (com.cmuxterm.app.nightly), and publishes it to the one feed the app polls, instead of building a cmux NEXT identity and re-signing a second copy for a separate feed. Implements #33050.
One signing, one feed
- Removes the NEXT-identity build, the second signing, and the copy jobs; the sign job's budget drops back to 160 minutes.
- The verdict workflow's retag now guards the one feed's own base (
nightly-next/, where its DMGs sit) instead of the verdicts' folder, so it no longer refuses every promotion. - The feed keeps the Nightly build before the newest one so a launch crash still has a build to roll back to, and an unreadable live feed now warns and publishes instead of stopping.
- The canary reads the one feed but pulls verdicts from the verdicts' folder (
nightly-next/next/), where the app reads them; deriving that path from the feed matched nothing, so every scheduled tick re-ran the canary and dispatched another verdict. - The retired feed holds a signed announcement card pointing stray cmux NEXT installs at cmux NIGHTLY; classic NIGHTLY (
files.cmux.com/nightly) is untouched. - Tests follow the one signing: the publish-path budget and rolling-name pins no longer reference the removed copy jobs.