ci(nightly-next): canary the signed app before and after publication

Merged
TE
19d ago
+547-7

Stacked on #21887 (the rollback). Review only the second commit.

Leo dogfoods cmux NEXT through its nightly auto-update, so the nightly needs a check that goes past "the process stays up". This adds a canary that launches the exact signed app on a CI Mac, never on a person's Mac, and checks:

  1. It opens a visible window.
  2. cmux tab new-page adds a tab to snapshot.get.
  3. The app's own acpmux starts a session of acpmux's test agent (fake_agent.py), and the agent answers a prompt. No account or network is needed.
  4. The app is still up 60 s after launch, with no cmux* or acpmux* crash report.

Reuse, not a new harness:

  • It is scripts/smoke-signed-app-cli.sh <app> --canary, an extension of the signed-app CLI smoke the nightly already runs. That smoke supplies the private socket launch, app ping / identify, and the cleanup.
  • The checks in scripts/cmux-next/signed-app-canary-checks.py reuse the readiness soak's control and acpmux clients and its fake-agent recipe.
  • Release builds serve no debug.* methods, so the checks use only snapshot.get, the bundled CLI and acpmux RPC. There's no pixel capture: "renders" means the app's model has the New Tab.

Two places it runs:

  • Before publication, verify-signed-nightly runs it on the app it then publishes. That covers the Mac writer.
  • After publication, nightly-next-canary.yml downloads the DMG the feed names. It checks the length, the notary tickets, Gatekeeper and the build number, then runs the canary from the build's own commit (CMUXCommit).
    • It covers the Linux writer and recovered builds too.
    • A build fails only after two failed attempts, and each build gets one verdict (a cache key per build).
    • The schedule runs only from main, so a main PR adds this file there. Here, the pull_request trigger runs it on the newest feed build with this PR's harness, and that run never pulls anything.

Switch: repository variable NIGHTLY_NEXT_CANARY. Unset means report-only.

ValueEffect
unset / reportBoth canaries run. A failure is an annotation plus one nightly-next canary failed issue. The build still publishes and stays on the feed.
enforceA pre-publication failure fails verify-signed-nightly, so nothing is published. A post-publication failure dispatches nightly-next-rollback.yml -f bad_build=<build>.
offSkips both.

Turning it on is one line: gh variable set NIGHTLY_NEXT_CANARY -R manaflow-ai/cmux --body enforce.

Testing:

  • bash -n and shellcheck are clean on smoke-signed-app-cli.sh. actionlint is clean on nightly.yml and the new workflow.
  • These pass: tests/test_ci_signing_keychain_is_deleted_before_bundle_code.py, test_ci_production_secrets_protected_env.py, test_nightly_next_recovery.py, test_nightly_trusted_routing.py.
  • Live run on the current feed build 3917159104301, from this PR's harness (run 39195075343): window win_80c1… is visible, a New Tab appears (kind conversation), a fake-agent session starts and answers end_turn, and the app stays up 60 s with no crash report. The first run (39194420604) failed on a wrong snapshot.get key. That run is how the bug was caught, and 5cad150 fixes it.
  • In this run the New Tab's id is pending_tab_…, so the check proves the app created the tab, not that it painted it.
  • Not run: the pre-publication step inside a real nightly. That happens on the first nightly-next run after this merges, in report mode.

Changelog

none

🤖 Generated with Claude Code