Repository navigation
cmux NEXT: opening an existing chat adopts the prewarmed page once its client is connected (carries #27759) - #31033
Conversation
A sidebar click, Open in Tab, a subagent's pane or any tab that shows an existing chat of this Mac now adopts the NewTabSparePool spare when it is connected and drew, and switches it to the chat in place (chat.select: the host's kept tail, then the attach), instead of building a page that loads, connects and attaches cold. The pool warms the next spare as after a Cmd-T. The spare never shows its New Tab screen in the chat's pane: the page leaves it in the adopt event's own render, and until it reports the chat drawn the pane keeps its cover (loading state or saved frame) with the page laid out outside the window, since a Chromium page draws in its own window over any cover. Excluded, with their own page as before: another machine's chats, Chief home chats (and an older subagent tab that home keeps), tabs with a seed, a New Tab page, or a kept or relaunch wake state, and a spare not yet connected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… anchor, says a missing chat is missing Review (codex gpt-6-astra) findings on the adoption path: - an adoption that arrives before the page client connects runs once its actions are in; - the chat opens where the reader left it (chat.scrollAnchor.get, as a cold open); - a chat the daemon does not list is the tab's missing chat, and a failed open with nothing drawn says it failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e and turn after a reconnect Second review (codex gpt-6-astra): an adoption whose anchor read finished after another chat was picked opened over it; one that a reconnect had already opened lost its subagent turn and scroll anchor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…turn Third review (codex gpt-6-astra): the anchor-read fence took a connect that opened the adopted chat itself for a newer pick, and dropped the adoption's subagent turn and anchor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the adoption import list in App.tsx (keep SessionAdoption and base's withHandshakeNewTab). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ttach Adoption awaited a full list reread before select() for a chat the spare's list lacked, so under a busy acpmux an older chat waited seconds for every list page. It now opens on its attach, as a cold open does (attachedBeforeList), with the list read again behind it; the list decides the chat is missing only when the attach fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…connected The spare served a tab opening an existing chat as soon as it had a handshake and a drawn New Tab frame, which come before its acpmux client connects. An adoption then queued behind the spare's whole first chat list (a busy acpmux paces it for seconds), where a cold page's open attaches the chat before its list (askEarly). Review of #27759 at cffa960 (BLOCK, P1). - The page reports its client connected or lost (`pane.clientReady`); a new handshake starts it over. NewTabSparePool.servesChat requires it. - No chat takes the spare in Low Power Mode or at serious thermal state: each would make another page. The tab opens cold, and the spare stays for New Tab. - A spare an existing chat took is replaced once input is quiet (750 ms), not on the next main-actor turn, so the new page's load does not land in that chat's open or the next sidebar clicks. New Tab and new workspace still refill at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
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)
Gallery before/after for
States
Pick other states with a Passes: CI passes on CI passes on Written by red: cmux-next checks (god files, concurrency, crash safety, l10n); NEW: Default changesThis pull request changes 1 default at
Signed off on this commit by Merge receipt: merged as Regenerate the snapshot with Shipped in:
|
|
Found 7 test failures on Blacksmith runners: Failures
|
|
Review (codex, review-31033-warm-a): SHIP at ebd97da No blocking findings. Follow-ups: capture the adoption transition (cold and cached opens); fill in the Performance table (in progress, chat-open bench before/after on a fleet display Mac). |
|
Review (next-review patch-id; carried): SHIP at 0196dec The independent SHIP at ebd97da carries to this head: the PR's own change ( |
|
Review (subagent claude-opus-5-5 medium): SHIP at 0196dec Base-merge delta from ebd97da (Codex SHIP there). The incoming base touches none of the PR's files (checked: empty intersection of changed paths). The only deletions are the two Cloud repository test files reverted by #31023, which nothing here uses. No blocking findings. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
|
Review (codex gpt-6-astra high, next-review): SHIP at 2f283ca SHIP at 2f283ca Visual verdict: unchanged by this diff
Follow-ups:
|
A newer verdict blocks 25938ec (no independent SHIP at the head).
|
Review (next-review patch-id; carried): SHIP at 25938ec The independent SHIP at 2f283ca carries to this head: the PR's own change ( |
…uched After tabState went passive, the same-host bench showed every open refusing the spare as touched by listProjects alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
|
Review (independent Codex gpt-6.1-sol, high, via the review router; full diff from merge base 93037ef to this head): SHIP at 098aec3. [P2] [follow-up] webviews/src/agent-session/acpmux/direct.ts:2081 - This checks whether the relisting Promise exists instead of whether it succeeded, so simultaneous transient list and attach failures falsely mark the adopted chat as missing. Gate |
…heir packages (#31456) The arch ratchet refused feat-cmux-next's tip with [app_lines] CmuxNextApp +282 against the last green arch tip cf0a547. This moves logic that holds no app state out of CmuxNextApp, behavior-neutral, and leaves the wiring in the App: - #31033 (spare page adopts an existing chat) to CmuxNextAgentPane: the adoption decision (AgentPaneSpareAdoption), the spare's readiness (AgentPaneSpareReadiness, AgentPaneModel.servesExistingChat), the refusal reason (AgentPaneModel.spareRefusal), the trace outcome (AgentPaneSpareOutcome), the pool's slot (AgentPaneSpareSlot) and refill policy (AgentPaneSpareRefill). - #30002 (Cloud chat repick) to CmuxNextAgentPane, next to AgentPaneCloudStart and AgentPaneCloudRepick: the started-chat record (AgentPaneCloudStarted) and the picked-machine match (AgentPaneCloudRepick.machineID(in:)). - #31363: CommandHistory exposes its TerminalCommandSuggestionStore (CmuxNextHistory) instead of four forwarding methods. - #31316: the feed handoff driver and its wire shape (FeedHandoffDriver, FeedHandoffWire) to CmuxNextAgentPane/Approvals, next to FeedHandoffPolicy, which it already depends on. The pure tests move with their code (AgentPaneSpareAdoptionTests, AgentPaneCloudStartTests). No new package edge. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>
…ted until its page draws the chat (#31486) #31033 turned adoption on, and the adopted page regresses: sidebar opens show an empty transcript and reopens never paint (HQ#5914). Leo, 2026-11-01: "i had to retry to get it to load???". The spare no longer serves an existing chat, which is the cold open from before #31033. The adopt path's fix follows. Agent-Session: https://cmux.com/deeplink/agent?id=aid-14c3a892e82255726cbb Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ttach fails adoptSession read the whole list beside the attach, on the same socket; a busy acpmux paces that list for seconds, and adopted reopens drew later than a cold page (HQ#5914 bench). The list now decides only a failed attach, and only a list that was read says the chat is missing (#31033's review follow-up). spare-session-adopt.test.ts: 1 fail on the previous commit, 7 pass here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-14c3a892e82255726cbb
… drawing while it is held off screen (#32448) * test(agent pane): a hidden adopted page still reports its paint (fails: it waits for a frame) The page's pane.painted report moves to paintReport.ts unchanged (one frame, then a task). paint-report.test.ts fails on it: a hidden document runs no animation frames, so the report never comes (HQ#5914). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-14c3a892e82255726cbb * cmux NEXT: an adopted spare page reports its paint while hidden, and existing chats take the spare again The parked spare can be hidden (Chromium marks the page hidden), and a hidden document runs no animation frames. The page's pane.painted waited for one, and the host keeps an adopted page away and covered until that report: reopens held the cover for the ~15 s give-up (HQ#5914; diag bench at 7d8c57e: visibilityState hidden, last frame before the report was asked). A hidden page now reports on a task, at once or when it goes hidden while it waits. The chat-open trace counts an adopted page as drawn once its pane painted: its first frame (page_painted) came before the trace, as New Tab, so every adopted open read page_empty. Turns adoption back on (#31486 switched it off). paint-report.test.ts: red on the previous commit (2 fail), green here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-14c3a892e82255726cbb * test(agent pane): an adopted page opens an unlisted chat with no list read beside its attach (fails) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-14c3a892e82255726cbb * cmux NEXT: the adopted page reads the chat list again only when its attach fails adoptSession read the whole list beside the attach, on the same socket; a busy acpmux paces that list for seconds, and adopted reopens drew later than a cold page (HQ#5914 bench). The list now decides only a failed attach, and only a list that was read says the chat is missing (#31033's review follow-up). spare-session-adopt.test.ts: 1 fail on the previous commit, 7 pass here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-14c3a892e82255726cbb * test(agent pane): an adopted page held outside the window keeps drawing under the pane's paint hold (fails) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-23bb1f8ee2895ef522be * cmux NEXT: an adopted page held outside the window keeps drawing under the pane's paint hold The pane's paint hold makes its incoming view transparent, and a transparent ancestor hides a Chromium page (#28002). An adopted New Tab spare waits 100 000 pt outside the window until it drew the chat (adoptSession), so the hold hid a page nobody could see: Chromium throttled it, and it drew the chat only once the hold gave up at 500 ms. A page laid out wholly outside its window's content now stays visible to Chromium whatever its ancestors' alpha; in place, a hold still hides it. Drops #31619's hidden-page paint report (on a task, without a frame): with the held page drawing, the report waits for a real frame again, and a report with no frame drawn showed the page's previous frame when the host revealed it. AgentPaneChromiumVisibilityTests.anAdoptedPageHeldAwayKeepsDrawingUnderAPaintHold: red on the previous commit, green here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-23bb1f8ee2895ef522be * test: cover selection after a failed spare adoption Agent-Session: https://cmux.com/deeplink/agent?id=aid-81082f85640c1fa29ca9 * fix: retain a newer selection after spare adoption fails Agent-Session: https://cmux.com/deeplink/agent?id=aid-81082f85640c1fa29ca9 * Agent pane: an adopted spare turns its cover on before its loading state begins The chat-open bench counted a flash on 7-10 of 10 adopted opens: its trace recorded the chat's saved frame ("image"), then the empty cover, then the chat. No such frame was drawn. beginLoadingState binds the trace and samples the surface at once, while the New Tab page's cover was still hidden; adoptSession turned the cover on only after that, in the same main turn, so the first sample read a state no frame showed. The cover now goes on first, so the pane is in its loading state from the first sample. Diagnosed on the fleet with a probe build that recorded every cover change after the adoption; it found none, which left the trace's own first sample. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-758f5f82cc10b26ecf3c --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
HOLD: with its gates fixed (098aec3) the spare is adopted on every open, and the adopted page regresses: sidebar opens show an empty transcript after the cover leaves; reopens hold the cover ~15 s and never paint. Not to land until the adopted page draws its chat and a same-host bench shows it faster.
Leo, 2026-11-01: "the loading for everything is just slow end to end / loading into a chat and some of its recent messages, loading the various ui elements of the metadata, etc. it's not cached well enough nor prerendered/prefetched well enough". Earlier, 2026-10-29: "as in i want ours to load fast too :(".
Finding 1 of the chat-open plan (manaflow-ai/cmuxterm-hq#5914): every tab not yet shown opens a cold page.
This PR carries #27759 (its 10 commits, unchanged) and fixes the blocking finding of its last review. #27759's lane has been idle since 04:32Z. #27759 stays open for its owner to close.
What changes
From #27759. Opening an existing chat of this Mac uses the prewarmed agent page (
NewTabSparePool) instead of building a new one. That covers a sidebar click, Open in Tab,chat.openTaband a subagent's pane. The page is already loaded, mounted and connected, so it switches to the chat in place (chat.select), drawing the host's kept tail when there is one, and then attaches.See #27759's body for the full design and its three earlier review rounds.
New here (the BLOCK at cffa960, P1). Before, a spare counted as ready once it had a handshake and a drawn New Tab frame. Both come before its acpmux client connects. An adoption that arrived then queued until
connect()finished, and that includes the spare's whole first chat list. A cold page attaches its chat before the list (askEarly/attachedBeforeList). So under load, adopting could be slower than the cold path it replaces.pane.clientReady {ready}): true onceconnect()finishes and its actions answer, false when the connection is lost. Each new handshake resets it (a reload or a reconnect).NewTabSparePool.servesChatrequires it. A spare that is still connecting stays in the pool, and the tab opens cold as before.Refill.whenQuiet), not on the next main-actor turn. A new page's load on the main thread would otherwise land inside that chat's open and the next sidebar clicks. New Tab and new workspace still refill at once (Refill.now).Spare cost
NewTabSpareSlot, a single slot). This PR adds no page.pool.makeSpare, and it happens only after quiet input on the chat path.PageWebView.snapshotParams). This PR changes neither when it is taken nor how.Performance
Measured with
scripts/measure/chat-open.sh(chat-open-bench.py, the app's owndebug.chat_open_tracefrom the click to the first frame of chat content), on a fleet display Mac. Base and head run in the same harness.Timers and observers
warmTimerand idle input monitor, which stop once the spare is made (warmNowremoves the monitor).Tests
SpareSessionAdoptionTests:thePageReportsItsClientReady: the request decodes; the state resets on a handshake; a ready report does not count as the person touching the page.whichSpareServesAChat: the readiness, Low Power Mode and thermal decision.Risk
Agent session: agent session
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Opening an existing chat now adopts the app's prewarmed, connected New Tab page instead of building a cold one. The page switches to the chat in place, keeping the reader's position and any subagent turn. Carries #27759's adoption path (sidebar click, Open in Tab,
chat.openTab, subagent pane).pane.clientReady); a spare still connecting stays in the pool and the tab opens cold.spare_adopted) or why not (spare_empty,spare_unready,spare_refused).Written for commit 098aec3. Summary will update on new commits.