cmux-next: Run board, run.attach bridge and retention reaper (rebuilt from #21305)
Summary
#21305 can't merge into feat-cmux-next anymore (37 conflicting files). Most of its durable Run API landed in reworked form in #21337 and #21634, and its priority sidebar landed in #21234 and #21281. This PR rebuilds the parts that hadn't landed on the current architecture, and fixes one bug in the landed code that the rebuild would otherwise have switched on.
Run board (webview, gallery only). webviews/src/agent-session/acpmux/run-board/ holds the board plus its client for /api/runs. The client reads a run's snapshot, replays events from a cursor, and mints and redeems a one-use attach ticket. Gallery entries cover the base board, the Compare and Timeline variants, and the top-two tournament judge.
Compared with #21305, the board:
- follows today's type-scale and layer tokens;
- restarts its replay session when the run changes;
- takes the session id from the Run view's
placement; - disables Attach when the view says the attempt isn't
attachable.
The board isn't mounted in the app. The agent page's CSP is connect-src 'none' and these routes need cmux.com auth, so mounting it means routing the fetches through native first.
run.attach (agent pane → App). The page sends run.attach with a redeemed ticket's ids, the worker session and a live ws/wss trusted-carrier endpoint. The App opens that terminal as a remote-terminal tab in the pane, and only under these conditions:
- the person made a fresh gesture in the pane, used once, as
tab.openrequires to run a line; - the target is a Cloud machine whose daemon is already connected and whose session matches the ticket;
- the target is never this Mac, an SSH host or a paired server.
The route is never dialed, and no token crosses the bridge. Nothing in the app sends run.attach yet (see above).
Retention reaper and artifact route (web).
- Retention cron. The landed dogfood path keeps a finished attempt's VM attachable for a retention window, and its comment says a cron destroys the VM afterwards. No route called
reapExpiredCloudVmAttempts, though./api/cron/run-attempt-retentionnow runs it every minute, monitored like the sibling crons. - Promoted VMs are skipped. While building this, review found that
claimExpiredAttemptCleanupswould also claim an attempt that had been promoted to a persistent runtime. Promotion keeps the same VM and doesn't clear the retention window, so the cron would have destroyed that runtime's live VM. The claim now skips attempts that have aruntime_id. - Already-gone VMs count as cleaned. A VM the reaper finds already gone is marked cleaned. Before, it failed every minute and blocked the rows behind it.
- Artifact route.
POST /api/runs/[id]/attempts/[attemptId]/artifactsexposes the landedrecordRunArtifactto workers. - Ported tests. #21305's tests for the run model, the provider view, worker auth and the live-database route suite come across. The route suite now expects a changed retry of the same event id to get a 409, which is #21634's rule.
Not rebuilt from #21305:
- Landed elsewhere: the durable Run/Attempt/Event API, retention and cancellation, attach-ticket minting, typed provider views, lease fencing (#21337, #21634) and the priority sidebar projection (#21234, #21281).
- Not landed, and not part of this PR:
- the agent broker, VM host-lease transport, Huge Blue relay and attach tokens, and persistent-workspace lifecycle wiring (#21456, #21502, #21503, #21559, #21569, #21581, which merged into #21305's branch and never reached
feat-cmux-next); - the off-main-actor PNG encode for window snapshots;
- the Codex voice capability constants;
- the orchestration lab gallery;
- the sidebar motion debug menus;
- the New Tab context changes, superseded by #22131's location row.
- the agent broker, VM host-lease transport, Huge Blue relay and attach tokens, and persistent-workspace lifecycle wiring (#21456, #21502, #21503, #21559, #21569, #21581, which merged into #21305's branch and never reached
Testing
- Webview tests:
cd webviews && bun test src/agent-session/acpmux/run-boardpasses, 25/25.bunx --bun vp check src/agent-session/acpmux/run-boardis clean. - Webview style checks:
check:layers,check:type-scale,check:system-blueandcheck:browser-lanespass.bun run checkas a whole fails on this checkout, but only on base files this PR doesn't touch:check:text-focusandcheck:dialog-keysfail on FileGrid, FilesUI, Screenshots and Browser Swift files;- oxfmt flags
src/pages/settings/*; - the typecheck can't find the ungenerated
pages/settings/generated/strings.json.
- Web unit tests:
cd web && bun test tests/run-view.test.ts tests/run-model.test.ts tests/run-worker-auth.test.ts tests/cron-auth.test.ts tests/backend-error-coverage.test.tspasses.bun run typecheck,bun run lint:complexityandbunx eslinton the changed web files are all clean. - Live Postgres (podman on huge-blue, migrations applied with
scripts/db-migrate.mjs), withCMUX_DB_TEST=1at ff6c6a5ee5a:run-route-db-behavior9/9;run-repository-db-behavior9/9;run-retention-db-behavior6/6;run-attempt-promotion7/7;runtime-promotion-db-behavior1/1.
- Promoted-VM regression: the new test
never reaps the VM of an attempt promoted to a persistent runtimefailed with the old claim (Expected: false, Received: true, 5/6 at bfb7f1e1c5a) and passes with the fix. - Swift:
xcrun swiftc -parseon every changed file passes, andscripts/cmux-next/prepush-gate.pyreports no new violations.AgentPaneRunAttachTestswas added; it covers decoding, refusal of a stale or untrusted endpoint, the single-use gesture gate and the missing-host reply.
Observed / Not run
Observed: everything listed under Testing.
Adopted from a desktop session with no live owner. Fixes since adoption: the expired-attempt cleanup claim only reaps ephemeral or runtime-less attempts, promoteAttemptToRuntime throws RuntimeAttemptConflictError while a cleanup lease is active, public import Foundation in AgentPaneRunAttach.swift, and size trims. Base merged at 8ae8fd5f2bd4 (clean).
At head 63031b46ce82:
- Fleet
cmux-ci compile-check --apppassed (job 2222dd98a78664c0dbdc20f8). - Fleet
run-swift-testing-suites.sh Packages/macOS/CmuxNext:AgentPaneRunAttachTestspassed. The run also failed 36 suites this PR doesn't touch (Accounts, ClassicUpgradeSimulation, FolderNameIndexSpeed, AgentPaneSendPickedFolderPrompt gesture checks, among others), so that run as a whole is not green. - CI reds are base only:
Web tests (1/4)fails "keyboard shortcuts docs > table aliases" andreact-apps-checkfails as on base. The CI Swift jobs were still queued at merge time.
Follow-up: runError in web/services/runs/http.ts doesn't map RuntimeAttemptConflictError to 409 yet.
Not run:
- CI's
cmux-next swift testhadn't run at merge time (queued). - The retention cron and the artifact route weren't exercised against a deployed backend.
run.attachhasn't been driven in a tagged build, because nothing in the app sends it yet.
Review: correctness review by a separate review subagent at eaca1abb783 (verdict: pass with fixes). Its findings are fixed in bfb7f1e1c5a..ff6c6a5ee5a; the same reviewer re-checked those at ff6c6a5ee5a (pass).
Runtime test: run-retention-db-behavior "never reaps the VM of an attempt promoted to a persistent runtime" plus the Run DB suites above, CMUX_DB_TEST=1 bun test --max-concurrency=1 <file> against Postgres 16 on huge-blue at ff6c6a5ee5a.
Changelog
none
Proof
Not UI: the Run board appears only in the webview gallery, whose advisory run captures it (agent-pane.run-board* entries). Nothing user-visible changes in the app.
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Rebuilds the Run board webview and run.attach bridge from #21305 on the landed durable Run API, and adds the run-attempt retention reaper and worker artifact route. Fixes three reaper bugs that would have destroyed promoted runtimes' VMs and blocked cleanup rows.
Run board and attach bridge
- The board reads a run's snapshot, replays events by cursor, and mints and redeems a one-use attach ticket; it's gallery-only, not mounted in the app.
run.attachopens the attempt's terminal as a remote-terminal tab only on a connected Cloud machine whose daemon session matches the ticket, with a fresh pane gesture; the route is never dialed and no token crosses the bridge.- CmuxNextApp stays under its size cap by joining the attach wiring onto fewer lines, with no behavior change.
run.attach's init imports Foundation publicly so the package builds withInternalImportsByDefault.
Retention reaper and artifact route
/api/cron/run-attempt-retentionruns the landed reaper every minute, monitored like the sibling crons.- The reaper skips attempts promoted to a persistent runtime, not every attempt with a runtime: each launched attempt already owns an ephemeral runtime, so claiming all of them would reap every finished VM.
- Promotion is refused while the reaper holds an attempt's cleanup lease, closing the race where promotion commits before the destroy and loses its VM.
- A VM the reaper finds already gone counts as cleaned instead of failing every minute and starving older rows.
POST /api/runs/[id]/attempts/[attemptId]/artifactsexposesrecordRunArtifactto workers.