ci(nightly-next): the feed goes live from R2; the GitHub release copy follows off its path

TE
10d ago
+501-112

Part 2 of 2. This PR is based on part 1 (the R2 publisher tooling) and gets retargeted to feat-cmux-next once part 1 lands.

Leo's words

Holy shit. How can you possibly be uploading for anything more than one minute? It's unacceptable. You find that shit. You unbottleneck it. Just... how are we even spending more than one minute on uploads? Are you kidding me? What even is this?

Before

Measured on publish-nightly in nightly.yml, step "Publish nightly release assets", with the feed waiting on it:

  • 777 s in run 40357982361
  • 1079 s in run 40376320918
  • 55 s when not throttled (run 40380339203)

About 95% of the throttled time is backoff on GitHub's secondary limit. The breakdown is in part 1.

Change (nightly-next only; main's channels are untouched)

  • publish-nightly, after the existing gates:
    • Uploads this build's DMG to files-next.cmux.com/nightly-next/next/<name> with the existing NIGHTLY_NEXT_R2_* token, then reads it back.
    • Points the feed item at it. These are the same bytes, so the EdDSA signature is unchanged.
    • Writes the feed with the compare-and-swap: written only while the live feed's newest build is below this one.
    • The GitHub release steps no longer run here on nightly-next.
  • New job publish-nightly-next-github, which nothing needs:
    • Prune, release assets (DMG, daemon files, rolling cmux-nightly-next-macos-arm64.dmg, feed copy), source archive, release body with the cmux-published-sha marker, and the tag move.
    • No concurrency group, so nothing evicts a waiting run of it. It can wait out rate limits for up to 60 minutes, inside a 150-minute timeout.
    • It re-checks the tested DMG digest.
    • It writes the mutable parts (rolling name, feed copy, body, tag) only while its build is still the feed's newest, so an older run that finishes later can't move them back. Immutable assets always upload.
  • republish-nightly-deltas on nightly-next:
    • Puts the deltas on R2 in parallel and points every enclosure R2 serves at its length there. Older builds from before R2 keep their release URLs.
    • Revises the feed only while it still serves this build, using If-Match.
    • The live feed check replaces the tag check, since the tag now moves later.
  • sign-nightly-identity-legacy downloads the published DMG from R2 first and falls back to the release.

Coordinator's four checks

  1. Installed NEXT apps accept the new host. files-next.cmux.com already serves their feed over HTTPS. The Info.plist ATS settings add only a loopback exception, so ATS allows any valid HTTPS host. Neither CmuxNextUpdater nor CmuxUpdater checks the download host; the delegate only logs item.fileURL. The feed isn't signed (no SURequireSignedFeed), and the DMG's EdDSA signature covers the same bytes. Older builds whose files are only on GitHub keep their release URLs. The rolling release name (UpdateManualDownloadRecovery) is still updated by the follow-up job.
  2. The rolling alias is off the critical path. It moved to the follow-up job, which waits on nothing.
  3. Newer only. The feed write is a compare-and-swap on the live object. The delta revision needs the live feed to serve this build. The follow-up job's mutable writes need this build to still be the feed's newest.
  4. No evicting group on the follow-up job. It has no concurrency group at all. #25477/#25490 (the release App token) haven't landed; once they do, this job's GitHub calls can take that token.

After

To be measured on the next natural nightly-next runs after landing: the time from the start of the R2 step to the feed write in publish-nightly, plus the follow-up job's own time. Expected: the DMG is a single 237 MB R2 PUT (about 5-15 s from Blacksmith), plus a HEAD check and the feed write.

Risk

Medium. This changes where cmux NEXT downloads come from, and it splits publication across two jobs.

  • If R2 refuses the upload, publish-nightly fails before the feed moves, the same as a failed release upload today.
  • If the follow-up job fails, the release copy and tag go stale, and decide's published_sha (read from the release body) lags. Later release notes then cover a longer span. The feed and installs are unaffected.
  • Not addressed yet: R2 objects under nightly-next/next/ (about 240 MB per build) need a retention prune. That's a follow-up.
  • No secrets, environments or permissions change. Every R2 write uses the step-scoped NIGHTLY_NEXT_R2_* token the feed write already uses.

Agent session: agent session

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.