Skip to content

Agent pane: checkpoints and cloud recovery read the link state, not the last emit's reason - #32044

Merged
teamleaderleo merged 4 commits into
feat-cmux-nextfrom
lane/web-base-red-link-state
Nov 2, 2026
Merged

teamleaderleo merged 4 commits into
feat-cmux-nextfrom
lane/web-base-red-link-state

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Cause

snapshot.connection names why the client emitted a snapshot ("connected", "session changed", a streamed event's kind, "history", ...), not whether the link to acpmux is up (newtab/chatStore.ts says so). Two sites in App.tsx read snapshot.connection === "connected" as "the link is up":

  • checkpoints' online (useCheckpoints): any later emit called setOnline(false), which marks checkpoints unsupported and tells the app pane.checkpointAvailability: false;
  • cloudChatRecoveryActive's connected argument: any later emit counted as a lost link.

#31983 made this the usual case: "session changed" now arrives right after a local chat opens, so checkpoints read offline just after a local chat opens.

Fix

A new helper, linkUp(connection) in model.ts, holds the derivation the footer's connected already used (up unless disconnected or connecting…). connected is now linkUp(snapshot.connection), declared before useCheckpoints, and both checkpoints and cloud recovery read it. Cloud recovery also stays active on a "failed" emit (connected && snapshot.connection !== "failed"). direct.ts emits "failed" when a chat's open fails while the socket stays open. That snapshot has no rows and no unavailable, which is set only while no chat is selected. The link is up, but the chat was never read, so a paused Cloud machine must still get its Resume card, as it did before. Checkpoints keep plain linkUp.

I found no other === "connected" in webviews/src that reads the agent pane's link state; the Cloud page's connection is its own real link state. direct.ts is unchanged.

Test

webviews/test/agent-pane-link-state.test.tsx mounts AcpmuxApp on a stand-in for the app's bridge, with git.capabilities serving checkpoints. It sends a later emit for the same chat and checks the pane's last pane.checkpointAvailability. With "session changed", "agent_message_chunk" or "history" it must stay true. With "disconnected" it must go false, which is the control. There is also a unit check of linkUp.

cmux-ci test -- bash -c 'cd webviews && bun test test/agent-pane-link-state.test.tsx'

  • Red at the test-only commit ef79aa1, before the fix (run 41417420243, job 6e1143793a4140fc8a6dc7bff1c6e564): 3 fail, 1 pass. The three later-emit cases read false; the disconnect control passes.
  • Cloud failed-open case, a test committed before its fix: at 39104fb (linkUp alone) it fails (run 41419434333, job 29ffbc9f149f4730881ea29ac1809cc7), and in the same job it passes with base's App.tsx. At 93ebdfe it passes: agent-pane-link-state.test.tsx 6 pass, cloud-chat-recovery.test.ts 6 pass, cloud-chat-resume.test.tsx 10 pass (run 41420395649, job 42122f567d8946a5ac94d0715eabf1da).
  • Green at the first fix 0c7b68e (run 41417561577, job 3913d8021a5a4cff9e4aa9abc20ff9ff): 5 pass. The same job ran each file in its own bun process, and all were green: cloud-chat-recovery.test.ts (6), cloud-chat-resume.test.tsx (10), and every git grep -l checkpoint webviews/test file (16 files, 0 fail). tsc --noEmit in the same job reported one error, in test/latency/probe.ts (PerformanceEventTiming.interactionId). This PR does not touch that file.

Changelog

Fixed: checkpoints no longer show as offline or unsupported just after a local chat opens, and a Cloud chat's recovery no longer treats an ordinary update as a lost connection.

Agent session: agent session

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 93ebdfe: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr32044 -f ref=lane/web-base-red-link-state -f platform=macos -f backend_mode=local

Gallery before/after for 93ebdfe: 16 states rendered, 0 images differ, 2967 more touched states not rendered. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 41420371785 -R manaflow-ai/cmux -n gallery-advisory-32044-93ebdfe77670862d0151ca9bb74d1cebf028c570 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.chat-width/standard Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/standard Apple System Colors unchanged 0% own App.tsx
agent-pane.chat-width/wide Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/wide Apple System Colors unchanged 0% own App.tsx
agent-pane.chat-width/wide-blocks Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/wide-blocks Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-edge/short-edge Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-edge/short-edge Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-edge/wide-edge Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-edge/wide-edge Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-edge/no-edge Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-edge/no-edge Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-floats/scrolled-to-end Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-floats/scrolled-to-end Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-floats/composer-grown Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-floats/composer-grown Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-40 Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-40 Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-56 Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-56 Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-72 Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-72 Apple System Colors unchanged 0% own App.tsx
agent-pane.continuation-spacing/shipped Apple System Colors Light unchanged 0% own App.tsx
agent-pane.continuation-spacing/shipped Apple System Colors unchanged 0% own App.tsx
agent-pane.continuation-spacing/two-thirds Apple System Colors Light unchanged 0% own App.tsx
agent-pane.continuation-spacing/two-thirds Apple System Colors unchanged 0% own App.tsx
agent-pane.continuation-spacing/half-tucked Apple System Colors Light unchanged 0% own App.tsx
agent-pane.continuation-spacing/half-tucked Apple System Colors unchanged 0% own App.tsx
agent-pane.continuation-spacing/before Apple System Colors Light unchanged 0% own App.tsx
agent-pane.continuation-spacing/before Apple System Colors unchanged 0% own App.tsx
agent-pane.copy-controls/output Apple System Colors Light unchanged 0% own App.tsx
agent-pane.copy-controls/output Apple System Colors unchanged 0% own App.tsx

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

Shipped in:

The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/41425819578 at f22cb91 failed: cmux-next checks (god files, concurrency, crash safety, l10n).
Those jobs last passed at e836232. This pull request is one of 5 merged since: #28950, #32044, #32084, #31945, #32102.
If the failure is in your change, fix forward on feat-cmux-next. Pull requests run only the tiers their paths reach (docs/ci/cmux-next-tiers.md); label a batch PR full-ci to run them all before merging.

…he last emit's reason

snapshot.connection names why the client emitted, not whether the link is
up. Checkpoints and cloud chat recovery compared it with "connected", so any
later emit ("session changed", a streamed event's kind) read as offline.
Both now use one linkUp() helper, the derivation the footer's `connected`
already used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-2f629b93d5a20c58e6f8
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium): SHIP at 0c7b68e

Checked every emit(...) reason in direct.ts: only "disconnected" and the opening state mean the socket is down; "failed" is emitted only while the socket is OPEN (else "disconnected"), and the rest ("session changed", "history", "replay", "resynced", "peer offline", stream kinds…) are emit reasons on a live link, so linkUp is the right reading and matches the footer's existing rule. Hoisting connected is a plain const (no hook order change, no shadowing, footer value identical). Recovery: cloudChatRecoveryActive's own comment targets "a disconnected native wire", so linkUp keeps its intent; the existing cloud-chat-recovery tests are unaffected. The new test mounts the real AcpmuxApp, waits for pane.checkpointAvailability true, and asserts it stays true per emit and goes false on "disconnected", so it is not vacuous.

Follow-ups: [follow-up] recovery narrows for a cloud chat whose open fails on an open socket ("failed", 0 rows, no snapshot.unavailable): before it engaged recovery, now it relies on the list path setting unavailable; worth a test that a paused cloud machine's failed open still shows the resume card. [follow-up] "peer offline" now reads as link-up for checkpoints (as the footer already did); checkpoint calls to an offline peer should fail quietly rather than flip availability.

@cursor

cursor Bot commented Nov 2, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

…oud recovery

direct.ts emits "failed" when an open fails while the socket stays open,
with no rows and no `unavailable` (set only while no chat is selected).
The link is up, but the chat was never read, so recovery reads its machine
as before #32044's linkUp change; checkpoints keep plain linkUp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-2f629b93d5a20c58e6f8
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium): SHIP at 93ebdfe

Visual verdict: unchanged by this diff.

Delta review from 0c7b68e: the only changes are the new link-state test and the one-line recovery input (connected && snapshot.connection !== "failed"). Checkpoints still use linkUp. The test sets up a Cloud chat with a "failed" emit, 0 rows and a paused machine, and it waits for and asserts resumeMachine. At 39104fb, linkUp("failed") is true, so recovery stays inactive and the test fails there. It is not vacuous. "peer offline" doesn't need the same treatment: direct.ts emits it only when a peer copy was drawn (drawPeerCopy > 0), so rows are > 0 and recovery is inactive on base as well.

Follow-ups: [follow-up] after a "failed" emit with 0 rows, a later emit that counts as link up (for example "session changed" or "commands") turns recovery inactive again. useCloudChatRecovery then resets, so the Resume card goes away. Base did the same on any later plain emit() ("connected"), so this is about as bad as base, not worse. Fix: make the failed open sticky until rows arrive or the selection changes.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 93ebdfe7 Deployed Nov 2, 2026 by teamleaderleo via cmux-tui tree preflight #100451
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant