ci(next): the tip suite dispatcher also runs the daemon suite on the feat-cmux-next tip
Problem
A feat-cmux-next push never runs cmux-next daemon tests. As a result, the base has had no daemon-test verdict for hours, and scripts/ci/base_red_excuse.py (via test_reds.py) refuses every pull request's daemon red with "no completed feat-cmux-next run of its workflow has a verdict". This blocks #27692, #27868 and others. The only base logs so far came from hand dispatches.
Change
The tip suite dispatcher (cmux-next-tip-swift-suite-dispatch.yml) runs the daemon suite the same way it runs the Swift suite. It gets a matrix leg per suite (swift, daemon), and each leg keeps its own cadence:
- The daemon leg asks
cmux-next.ymlonrefs/heads/feat-cmux-nextfor atip_daemon_suiterun. - It dispatches right away when the diff since the last daemon verdict reaches the router's daemon tier (
cmux_next_route.pydaemon=). This uses the same router step as Swift, now reading the leg's own tier. - It also dispatches once the tip is 20 landings past the last daemon verdict, whatever changed, so the base log stays fresh.
- A green per tested commit is reused: a tip that already has a verdict is never run again. A tip with no daemon-tier change since the last green is not run until the 20-landing floor.
- Runs are matched by name (
cmux-next tip daemon suite · <tested sha>), and the tested commit is read from the name. A same-tree run that tests a recorded sha after the tip moved still counts for that sha. - At most one run per suite per tick, never while one is in flight or one started in the last 25 minutes. A tip with no verdict is retried at most 3 times.
- Until the companion
feat-cmux-nextPR lands, the tip'scmux-next.ymlrefusestip_daemon_suite(HTTP 422). The daemon leg warns on that instead of failing the tick.
There are no new permissions or secrets. The job keeps its existing actions: write / contents: read and its existing runner expression.
Companion PR on feat-cmux-next: the tip_daemon_suite input, its tree-wait handling, and the base_red_excuse.py lookup (linked in a comment below).
Performance
Runner minutes added per base push (estimate). The daemon-test job took 9 to 13 minutes in the hand dispatches 41024114885 and 41001517417. The base gets about 30 pushes an hour, and the daemon leg starts at most 2 runs an hour.
| metric | before | after | delta | harness or run link |
|---|---|---|---|---|
| Mac runner-minutes per base push (daemon-test) | 0 | ≤ 0.9 (≤ 2 runs/h × ~13 min ÷ ~30 pushes/h) | +≤ 0.9 | runs 41024114885, 41001517417 |
| Linux runner-minutes per tick (dispatcher) | ~1 (one leg) | ~2 (two legs) | +~1 per 30 min | this workflow |
| Base daemon-test verdict age | unbounded (hours, hand dispatch only) | ≤ ~1 h after a daemon-tier landing, ≤ 20 landings otherwise | n/a | plan tests |
Tests
python3 tests/test_cmux_next_tip_swift_suite_dispatch.py passes. New cases cover:
- daemon dispatch inputs
- swift and daemon runs never standing in for each other
- the tested sha read from the name, not head_sha
- a skipped daemon job counts as no verdict
- the 20-landing floor (and Swift has none)
- per-suite cadence
- the 422 warning
Existing Swift cases are unchanged.
Agent session: agent session
🤖 Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Makes the tip suite dispatcher also run the daemon suite on feat-cmux-next, so base pushes get a daemon-test verdict for base_red_excuse.py to compare pull request daemon reds against. Previously feat-cmux-next never ran daemon tests, leaving PRs like #27692 and #27868 blocked.
Changes
- The dispatcher gets a matrix leg per suite (
swift,daemon), each with its own cadence and one run per tick. - The daemon leg dispatches
tip_daemon_suitewhen the router's daemon tier is reached, and also once the tip is 20 landings past its newest verdict — red or green — whatever the diff. - Runs are matched by name and their tested commit, so a same-tree run still counts.
- A daemon run whose tree was still publishing (daemon-test skipped) is pending for 90 minutes, letting its same-tree run give the verdict; past that it is retried as a lost run. Swift has no deferred state.
- Only the unknown-input 422 warns; any other dispatch failure fails the tick.
- The owned-pool rescue now wakes on a failed tip daemon suite run when it completes.
- No new permissions or secrets.
Summary by CodeRabbit
- Chores
- CI tracks Swift and daemon test suites independently, using suite-specific results and change routing.
- Daemon checks are dispatched when the tip is at least 20 commits past the newest verdict. Skipped daemon checks may be deferred for up to 90 minutes while the tree publishes; skipped Swift checks are retried immediately.
- A refusal to dispatch a daemon check is reported as a warning; other dispatch errors remain errors.
- Failed daemon-suite runs are now recognized by the CI rescue workflow.