Repository navigation
cmux-next: Run board, run.attach bridge and retention reaper (rebuilt from #21305) - #22408
Conversation
Rebuilt from #21305 onto the landed Run API (#21337, #21634). The board reads a durable run's snapshot and replays its events by cursor, mints and redeems a one-use attach ticket, and asks the App to open the worker terminal (run.attach). Gallery entries: the base board, the Compare and Timeline variants, and the top-two tournament judge. Adapted to current lints: type-scale and layer tokens instead of raw sizes and z-index, an output element for attach status, and a narrow fetch type so tests pass plain functions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (run.attach) Rebuilt from #21305. The pane decodes run.attach (ids, a live trusted-carrier ws/wss endpoint, the worker session) and, with the person's fresh gesture in the pane, asks the App to open the attempt's terminal as a remote-terminal tab. The App opens it only on a connected remote worker daemon whose session is the one the ticket names; the route is never dialed and no token is accepted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landed Run API schedules a short terminal retention window after an attempt finishes and its comment says a cron reaper destroys the VM after it, but no route called reapExpiredCloudVmAttempts, so retained VMs were never destroyed by it. Add /api/cron/run-attempt-retention (monitored, every minute) and the worker artifacts route over the landed recordRunArtifact. Port #21305's tests for the run model, provider view, worker auth and the live-database route suite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aper Fails on the base: claimExpiredAttemptCleanups claims any finished attempt whose retention expired, including one promoted to a persistent runtime, so the retention cron would destroy the runtime's live VM. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claimExpiredAttemptCleanups now skips attempts linked to a runtime: promotion hands the attempt's VM to a persistent runtime without clearing the retention window, so the new cron would destroy it. A VM the reaper finds already gone now counts as cleaned instead of failing every minute and starving older rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The board rebuilds its replay session when the run id changes, reads the latest snapshot without a stale closure (vp check's exhaustive-deps), takes the session id from the view's placement and disables Attach when the view says the attempt is not attachable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
daemon(machine:) also resolves SSH hosts and paired servers; the ticket names a Cloud VM, and the page's fields are not verified natively, so the lookup is now Cloud-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Unclear: CI failed on
Failures:
A web test can import code this PR changes without being one of its files: Matched log linesNot re-run automatically: Written by red: web / Web status; base red: the same failure on the base 8ae8fd5; does not block
Agents: Tour of |
|
Found 20 test failures on Blacksmith runners: Failures
...and 10 more test failures. View all on Blacksmith
|
|
Bead |
[Medium impact] The changes affect Run workflows in the web service and the macOS agent pane. Fix the retention filter before merging; it leaves ordinary finished Run VMs running. FindingsSummaryAdds gallery Run boards, a native
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
Launch[Launch ordinary attempt] --> Ephemeral[Create ephemeral runtime]
Ephemeral --> Link[Set attempt runtimeId]
Link --> Finish[Attempt finishes]
Finish --> Retain[Retention window expires]
Retain --> Filter{runtimeId is null?}
Filter -->|No| Skip[Cleanup skips VM]
Filter -->|Yes| Destroy[Destroy VM]
Reviews (1) · Last reviewed commit: "fix(cmux-next): run.attach opens only on..." · Reviewed by Greptile |
| // A promoted attempt's VM belongs to its persistent runtime now; never reap it. | ||
| isNull(runAttempts.runtimeId), |
There was a problem hiding this comment.
runtimeId does not mean an attempt was promoted. launchCloudVmAttempt creates an ephemeral runtime and links every attempt to it before creating the VM. This filter therefore skips ordinary finished attempts too, so the new cron never destroys their retained VMs. They keep using paid resources and can fill the user's machine allowance.
Check whether the linked runtime is persistent instead of excluding every non-null runtimeId. Add a regression test using an attempt linked to an ephemeral runtime.
| cursor = result.nextCursor; | ||
| this.transportCursor = cursor; |
There was a problem hiding this comment.
readPages saves its cursor before returning the fetched events. If page one succeeds and page two fails with a network error or HTTP 503, replay throws without applying page one's events. The next retry starts after that saved cursor, permanently skipping those activity and status updates.
Keep cursor changes local until the whole read succeeds, and add a test where the second page fails once.
|
|
|
|
|
Orphaned: no activity for 14 h, and no lane holds a lease on it. The orphan sweep could not merge |
|
Owned by gallery-loop-b4 on cosmic-gray · session |
AgentPageProvider's method list keeps base's pane.setDisplay and this PR's run.attach. Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
The size cap refuses any source growth in CmuxNextApp. The run.attach closure and its onRunAttach hook are joined into fewer lines, and four one-statement functions and three guard blocks in the same two files are written on one line each. No behavior change. Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
Base now builds CmuxNextAgentPane with InternalImportsByDefault, so AgentPaneRunAttach's public init(params:now: Date = Date()) failed to compile (fleet compile-check 3476a896 at 208bdff). Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
…l runtime (failing) Review of #22408: launchCloudVmAttempt gives every attempt an ephemeral runtime, so skipping any attempt with a runtime_id skips every real Run VM and none is ever destroyed. The promoted-VM test now inserts its runtime as persistent explicitly. Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
…istent runtime Every launched attempt is attached to its own ephemeral runtime before its VM exists, so the claim now left-joins cloud_runtimes and keeps an attempt whose runtime is ephemeral; the row lock stays on run_attempts (FOR UPDATE cannot lock the nullable side of an outer join). Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
…cleanup lease (failing) Review of #22408: a promotion that commits after the reaper's claim but before destroyVm still loses its VM. Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
…p its VM Agent-Session: 01a15ba2-c7ad-7850-9e71-5b51f1c9ed9f
…d focus (base text-focus red) (#23300) #22408's timeline.css put an outline on '.timeline-board input:focus-visible', which check-text-focus-rings reads as a ring on a text field and fails the base 'web lint (fast)' job. The board's only input is the review checkbox; the selector now says so. Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ble (#23301) #22408 wrote the run board's attach and replay errors and the "Agent output" row title as English literals, which test/pane-english.test.ts rejects. These errors reach the board's alert through error.message, so they are translated: eight runBoard.* keys in acpmux/Localizable.xcstrings with all 21 languages, read through translate(). Error codes are unchanged. Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c78-27ca-7140-a6c7-95f0db83b18c&host=huge-blue Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Shipped in:
|
…icks (#23563) * test(gallery): agent-pane plays reach their state through the matrix's trusted input The agent-pane entries test now installs the gallery matrix's trusted input driver (trustedGalleryInput: cmuxGalleryInput through Playwright's mouse and keyboard) on every page, and a case can set the page size of a smaller matrix frame. It adds eight states that fail today because the driver clicks a target that is not on screen, so nothing receives the click and the play's waitFor never holds: - run-board-timeline#approved, run-board-tournament#approve-winner, run-board-tournament#select-review, run-board.compare#approve-winner: the review checkbox and approve button sit below the fold. - composer-row#fast-off and #fast-on (720x300), composer-pickers#fast-mode (640x260): the model menu scrolls in the short frame and its Fast mode row is half clipped. - artifact-panel#versions (760x640): the first draft's Open preview is scrolled out above the latest reply. The run-board states and artifact-panel#versions check the play's error only. Their steps shift layout by design or by a width follow-up: the compare receipt and the approve button change width (the receipt anchor moves 92 px), and the side panel narrows the chat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c2b-c6e5-7540-ad96-0eb60604230e&host=huge-blue * gallery: trusted play input scrolls its target into view before it clicks The matrix runner's trusted driver (cmuxGalleryInput) clicks at the target's on-screen point, and playRunner never scrolled the target into view first. A target below the fold or scrolled out of a short frame got no click, so the play's waitFor never held. Before a trusted input step, the target now scrolls into view the way a person would bring it on screen, outside the measured step so the scroll is not counted as an anchor move or a layout shift. Synthetic input is unchanged. This reaches the run-board review and approve states (timeline#approved, tournament#approve-winner, tournament#select-review, compare#approve-winner), broken since the run board landed in #22408; their four lines leave broken-allowlist.json. It also fixes composer-row#fast-off, composer-row#fast-on, composer-pickers#fast-mode and artifact-panel#versions in the matrix's short frames: #23473 dropped those keys from the allowlist, and they failed in gallery health on #23498. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c2b-c6e5-7540-ad96-0eb60604230e&host=huge-blue * test(gallery): image-viewer#open and #dismiss-and-restore reach the viewer under trusted input Their plays click the first reply image, which is above the fold in the transcript. With playRunner's scroll into view removed, both cases fail (0 pass, 2 fail). With it, both pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c2b-c6e5-7540-ad96-0eb60604230e&host=huge-blue * gallery: image-viewer#open and #dismiss-and-restore render, so they leave the allowlist When trusted input scrolls the target into view, the first reply image is clicked. This brings back both states, which gallery health now reports as rendered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c2b-c6e5-7540-ad96-0eb60604230e&host=huge-blue --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
#21305 can't merge into
feat-cmux-nextanymore (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:
placement;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 sendsrun.attachwith a redeemed ticket's ids, the worker session and a livews/wsstrusted-carrier endpoint. The App opens that terminal as a remote-terminal tab in the pane, and only under these conditions:tab.openrequires to run a line;The route is never dialed, and no token crosses the bridge. Nothing in the app sends
run.attachyet (see above).Retention reaper and artifact route (web).
reapExpiredCloudVmAttempts, though./api/cron/run-attempt-retentionnow runs it every minute, monitored like the sibling crons.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.POST /api/runs/[id]/attempts/[attemptId]/artifactsexposes the landedrecordRunArtifactto workers.Not rebuilt from #21305:
feat-cmux-next);Testing
cd webviews && bun test src/agent-session/acpmux/run-boardpasses, 25/25.bunx --bun vp check src/agent-session/acpmux/run-boardis clean.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;src/pages/settings/*;pages/settings/generated/strings.json.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.scripts/db-migrate.mjs), withCMUX_DB_TEST=1at ff6c6a5:run-route-db-behavior9/9;run-repository-db-behavior9/9;run-retention-db-behavior6/6;run-attempt-promotion7/7;runtime-promotion-db-behavior1/1.never reaps the VM of an attempt promoted to a persistent runtimefailed with the old claim (Expected: false, Received: true, 5/6 at bfb7f1e) and passes with the fix.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,
promoteAttemptToRuntimethrowsRuntimeAttemptConflictErrorwhile a cleanup lease is active,public import FoundationinAgentPaneRunAttach.swift, and size trims. Base merged at 8ae8fd5 (clean).At head 63031b4:
cmux-ci compile-check --apppassed (job 2222dd98a78664c0dbdc20f8).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.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:
runErrorinweb/services/runs/http.tsdoesn't mapRuntimeAttemptConflictErrorto 409 yet.Not run:
cmux-next swift testhadn't run at merge time (queued).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 eaca1ab (verdict: pass with fixes). Its findings are fixed in bfb7f1e..ff6c6a5; the same reviewer re-checked those at ff6c6a5 (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 ff6c6a5.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-botwith what you need.Summary by cubic
Rebuilds the Run board webview and
run.attachbridge 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
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.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.POST /api/runs/[id]/attempts/[attemptId]/artifactsexposesrecordRunArtifactto workers.Written for commit 63031b4. Summary will update on new commits.