Skip to content

fix(webviews): relay local daemon peer state events - #27971

Closed
teamleaderleo wants to merge 27 commits into
feat-cmux-nextfrom
lane/nx-peer-events-restarting-1030
Closed

teamleaderleo wants to merge 27 commits into
feat-cmux-nextfrom
lane/nx-peer-events-restarting-1030

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Takes over #26885 (its lane is gone) with its commits and authorship, rebased onto current feat-cmux-next, and fixes the review finding that blocked it.

Local daemon peer transitions do not reach the web session's peer state today: the picker only sees the last _acpmux/status read. This relays them.

  • A zero-parameter _acpmux/peers relay in both host policies: the macOS host (AcpmuxPaneMethods) and the shared Rust policy (cmux-agent-pane-policy/policy.json), so a Rust host doesn't refuse the request. The reply is filtered to name, state, lastSeen and error, never URLs or credentials. The Rust ReplyShape gains Number (a finite number, never a boolean, matching Swift's .number) so lastSeen survives the filter.
  • The web client reads one snapshot once the chat list's _acpmux/watch answers, on each reconnect and after an _acpmux/lagged notice, then applies _acpmux/peer_state_changed notes from the same connection. It never polls. The snapshot isn't on the first frame's path: it lands and draws on its own, so the first frame keeps cmux-next: a chat's first frame waits on one daemon round trip #27755's one round trip.
  • Notes, snapshot reads and _acpmux/status reads are ordered on one clock (the tick each read started at). An older read of either kind can't overwrite a newer note or a newer read, and a status read replaces any entry written before it started.
  • It only turns on when initialize advertises peerStateEvents from a local origin. Peer, remote and unknown origins and older daemons keep the existing snapshot behavior.

The review finding, fixed here

The earlier review blocked #26885 at fbb1240e because peerStateEvents.ts accepted only the five PeerLinkState values. _acpmux/peers summaries use PeerState::as_str (cmux-tui/crates/acpmux/src/peer_link.rs), which also sends restarting while a peer's daemon restarts (peer_state_changed maps that to reconnecting). The adapter dropped that row, so the authoritative snapshot cleared the peer's state and connected, and environmentReady (state undefined, connected !== false) treated the peer as ready. The adapter now accepts every state the daemon sends. A restarting peer keeps state: "restarting" and connected: false, so it is listed but not offered as ready, the same as the _acpmux/status path already does on base.

Later reviews of this branch found more:

  • A second path to the same result: a peer that _acpmux/status lists but the authoritative snapshot omits (removed between the two reads, or a row the adapter could not read) also lost its state and connected. applyPeerStates now marks such a peer not connected, keeping the lastSeen and attempts the offline banner shows.
  • A snapshot that said restarting never cleared if the restart then failed. Restarting and reconnecting are both reconnecting on the wire, so the daemon sends no note, and the snapshot kept overriding newer status reads. A status read now replaces it (the one-clock fence above), and chatUnavailable counts restarting as still connecting.
  • The Rust relay policy didn't list _acpmux/peers (above). frames.json had sampled it as a method the pane never sends; that refusal sample moved to _acpmux/peer_remove, which stays unlisted.

Checks on the original

  • defaults guard: the "default" is the replyShapes table hash (AcpmuxPaneMethods+ReplyShapes.swift, the new _acpmux/peers reply filter). It is a relay reply allowlist, not a setting default, and it changes no user-facing default. The guard clears with an independent review receipt at the head.

  • check-fixed-waits (web lint): red on base. It flags chatOpenTrips.test.ts, added by cmux-next: a chat's first frame waits on one daemon round trip #27755, which this PR doesn't touch, so I pushed with a CMUX_WEB_LINT_SKIP_REASON naming the base sha.

  • tests/swift.rs the_known_params_are_the_macos_hosts: red on base (acp.session.fork's live param is missing from policy.json); this PR's _acpmux/peers entries match the Swift tables.

No capture: no new or changed UI. This keeps the existing picker rows' existing state labels current from daemon events, and a real peer transition can't be staged in a capture scene.

Relay analysis

_acpmux/peers takes no parameters (any parameter is refused, AcpmuxPanePeerSnapshotTests) and runs no command. It returns only the local daemon's peer names and link state, which _acpmux/status already relays. Only the local origin may consume it, so a peer daemon's connection never sees this host's peer inventory (#26860).

Testing

Each fix has its red test committed first:

  • test(webviews): reproduce a restarting peer dropped from the peer snapshot: cd webviews && bun test src/agent-session/acpmux/peerStateReducer.test.ts src/agent-session/acpmux/peerStateEvents.test.ts gives 16 pass, 2 fail (the reducer drops the restarting row; the client never reports restarting, so the picker reads the peer as ready). After the fix: 18 pass.

  • test(webviews): reproduce a listed peer the peer snapshot omits reading as ready: bun test src/agent-session/acpmux/peerStateEvents.test.ts gives 10 pass, 1 fail (peerConnected is undefined). After the fix: 11 pass.

  • The restarting-state, first-frame and chatUnavailable reds: bun test src/agent-session/acpmux/peerStateEvents.test.ts 11 pass, 2 fail, and bun test test/chat-unavailable.test.ts 1 fail; then green.

  • The out-of-order reads: bun test src/agent-session/acpmux/peerStateEvents.test.ts 13 pass, 2 fail; then green.

  • The Rust policy: cargo test -p cmux-agent-pane-policy on CI (cmux-ci). Parity replies failed at the red commit; at the head, parity passes 16 of 16.

At the head, the peer-state, reducer, environments and chatUnavailable suites give 30 pass, 0 fail.

Observed / Not run

  • Observed: the focused Bun suites above, red then green; scripts/web-lint.sh (the type-aware lint and type check are left to CI on this host); scripts/cmux-next/prepush-gate.py, no new violations.
  • Not run: the native AcpmuxPanePeerSnapshotTests (left to CI's Swift lanes); a real remote-machine transition. A run of the whole src/agent-session/acpmux folder in one Bun process timed out on CI, so the PR's own web test jobs cover the rest.

Changelog

  • Fixed: the machine picker follows local peers connecting, dropping and restarting live, and a restarting or just-removed machine is no longer offered as ready.

Agent session: agent session

🤖 Generated with Claude Code

teamleaderleo and others added 8 commits October 30, 2026 03:56
On base 1f723c6, bun test src/agent-session/acpmux/peerStateEvents.test.ts runs 7 tests: 3 fail and 4 pass. The client never reads peers and ignores the local state feed. Nonlocal and feature-absent fallback cases pass. Native validation is held by #26809.

Agent-Session: https://cmux.com/deeplink/agent?id=aid-93c3a66aa0c6bbfa650c
Gate peer events on the advertised feature and local/app origin. Read once after watch at initialize/reconnect, retain exact daemon state, clear nullable metadata, deduplicate lag replay, and reject superseded sockets/reads. Add a zero-parameter pane snapshot relay filtered to name, state, lastSeen and error. Preserve picker presentation and policies.

Linux web validation: 14 focused tests / 62 assertions pass; bun --bun run check passes including formatting, lint, typecheck (2933 files), and protocol checks. The focused regression was red before repair (3 failed / 4 passed). Native relay tests were added but native build/test/parse and CI dispatch remain held by #26809/HQ4779. No real-machine capture is claimed.

Agent-Session: https://cmux.com/deeplink/agent?id=aid-93c3a66aa0c6bbfa650c
The replacement socket must initialize its own local/app origin and peerStateEvents feature before receiving peer notes; invalidate prior read tokens at open start. Focused Linux web tests: 15 pass, 66 assertions. Full bun --bun run check passes. Native verification and CI remain held by #26809/HQ4779.

Agent-Session: https://cmux.com/deeplink/agent?id=aid-93c3a66aa0c6bbfa650c
The daemon drops peer_state_changed notices when a client lags and says so
with _acpmux/lagged; resyncing only the selected chat left a machine that
went down meanwhile shown as connected. Review P2 on #26885.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-710a4a518c009a6ad126
…pshot

_acpmux/peers says `restarting` (peer_link.rs PeerState::as_str) while a
peer's daemon restarts. The adapter drops that row, so the authoritative
snapshot clears the peer's state and the picker reads it as ready.

Red: bun test src/agent-session/acpmux/peerStateReducer.test.ts
src/agent-session/acpmux/peerStateEvents.test.ts: 16 pass, 2 fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-c35ebd1681cf79649972
_acpmux/peers names every PeerState acpmux has, `restarting` included
(peer_link.rs as_str), while peer_state_changed maps it to reconnecting.
The adapter now accepts it, so a restarting peer keeps its state and
reads not connected instead of falling back to ready in the picker.

Green: bun test src/agent-session/acpmux/peerStateReducer.test.ts
src/agent-session/acpmux/peerStateEvents.test.ts: 18 pass, 0 fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-c35ebd1681cf79649972
…ng as ready

A peer that _acpmux/status lists but the authoritative _acpmux/peers
snapshot omits (removed between the two reads, or an invalid row) loses
its state and `connected`, so environmentReady offers it as ready.

Red: bun test src/agent-session/acpmux/peerStateEvents.test.ts: 10 pass, 1 fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-c35ebd1681cf79649972
An authoritative _acpmux/peers snapshot replaced the peer maps with its
own rows, so a peer the status read still lists lost its state and
`connected` and the picker offered it as ready. Such a peer now reads
not connected until a note or the next read says otherwise.

Green: bun test src/agent-session/acpmux/peerStateEvents.test.ts: 11 pass, 0 fail.

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

Copy link
Copy Markdown
Collaborator Author

Review (gpt-6.1-sol): SHIP at 1ffadb3

No verified correctness defects found.

The change accepts all daemon snapshot states, keeps restarting non-ready, marks snapshot-omitted peers disconnected in webviews/src/agent-session/acpmux/direct.ts:6829, fences reconnects and reads, and filters _acpmux/peers to the intended fields.

Focused Bun tests pass: 19 tests, 76 assertions. Fleet validation was unavailable because the broker socket was inaccessible.

@blacksmith-sh

blacksmith-sh Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Found 6 test failures on Blacksmith runners:

Failures

Test View Logs
main.DeveloperIdAppTests.test_download_prints_the_active_profile_only/
test_download_prints_the_active_profile_only
View Logs
stubs: /System/Library/Frameworks/Security.framework/Versions/A/Security has no stub/
Security has no stub
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_CFRetain']/
CoreFoundation, missing 1: ['_CFRetain']
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_log2']/
CoreFoundation, missing 1: ['_log2']
View Logs
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)/
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)
View Logs
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)/
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 6c92cc2: 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=pr27971 -f ref=lane/nx-peer-events-restarting-1030 -f platform=macos -f backend_mode=local

Passes: CI passes on 6c92cc220a.

CI passes on 6c92cc220a (run 41158916705 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

web and size checks: green

Default changes

This pull request changes 1 default at 6c92cc220abe (what everyone gets without configuring anything):

  • tunable-source.CmuxNextAgentPane/AcpmuxPaneMethods+ReplyShapes.swift:replyShapes: "26b9b7cd6584a7628355be27d4708454d28a0e1fdc114e11f2b38a3e0d83aeef" -> "9d7db8a517c14300a974ef37ba085a77ee41ab355ebf8d8b27fddfc69a78f235"

Signed off on this commit by review (gpt-6.1-sol).

Regenerate the snapshot with python3 scripts/ci/defaults_guard.py snapshot.

Gallery before/after for 6c92cc2: 16 states rendered, 4 images differ, 2554 more touched states not rendered. Images are in the run's artifact, not on this public PR.

gh run download 41158916537 -R manaflow-ai/cmux -n gallery-advisory-27971-6c92cc220abec90b288c574e98a2667fbed593f2 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.live-chat-watch/choice-alone Apple System Colors Light unchanged 0% own direct.ts
agent-pane.live-chat-watch/choice-alone Apple System Colors unchanged 0% own direct.ts
agent-pane.live-chat-watch/view-only Apple System Colors Light unchanged 0% own direct.ts
agent-pane.live-chat-watch/view-only Apple System Colors unchanged 0% own direct.ts
agent-pane.auth-health/signed-in Apple System Colors Light unchanged 0% imports direct.ts (1 hops)
agent-pane.auth-health/signed-in Apple System Colors unchanged 0% imports direct.ts (1 hops)
agent-pane.auth-health/signed-out Apple System Colors Light unchanged 0% imports direct.ts (1 hops)
agent-pane.auth-health/signed-out Apple System Colors unchanged 0% imports direct.ts (1 hops)
agent-pane.auth-health/expired Apple System Colors Light unchanged 0% imports direct.ts (1 hops)
agent-pane.auth-health/expired Apple System Colors unchanged 0% imports direct.ts (1 hops)
agent-pane.auth-health/unknown Apple System Colors Light unchanged 0% imports direct.ts (1 hops)
agent-pane.auth-health/unknown Apple System Colors unchanged 0% imports direct.ts (1 hops)
agent-pane.chat-width/standard Apple System Colors Light unchanged 0% imports direct.ts (1 hops)
agent-pane.chat-width/standard Apple System Colors unchanged 0% imports direct.ts (1 hops)
agent-pane.chat-width/wide Apple System Colors Light unchanged 0% imports direct.ts (1 hops)
agent-pane.chat-width/wide Apple System Colors unchanged 0% imports direct.ts (1 hops)
agent-pane.chat-width/wide-blocks Apple System Colors Light unchanged 0% imports direct.ts (1 hops)
agent-pane.chat-width/wide-blocks Apple System Colors unchanged 0% imports direct.ts (1 hops)
agent-pane.cloud-chat-recovery/before Apple System Colors Light changed 0.0433% imports chatUnavailable.ts (1 hops)
agent-pane.cloud-chat-recovery/before Apple System Colors changed 0.0408% imports chatUnavailable.ts (1 hops)
agent-pane.cloud-chat-recovery/paused Apple System Colors Light unchanged 0% imports chatUnavailable.ts (1 hops)
agent-pane.cloud-chat-recovery/paused Apple System Colors unchanged 0% imports chatUnavailable.ts (1 hops)
agent-pane.cloud-chat-recovery/resuming Apple System Colors Light changed 0.0237% imports chatUnavailable.ts (1 hops)
agent-pane.cloud-chat-recovery/resuming Apple System Colors changed 0.0223% imports chatUnavailable.ts (1 hops)
agent-pane.cloud-chat-recovery/deleted Apple System Colors Light unchanged 0% imports chatUnavailable.ts (1 hops)
agent-pane.cloud-chat-recovery/deleted Apple System Colors unchanged 0% imports chatUnavailable.ts (1 hops)
agent-pane.composer-edge/short-edge Apple System Colors Light unchanged 0% imports direct.ts (1 hops)
agent-pane.composer-edge/short-edge Apple System Colors unchanged 0% imports direct.ts (1 hops)
agent-pane.composer-edge/wide-edge Apple System Colors Light unchanged 0% imports direct.ts (1 hops)
agent-pane.composer-edge/wide-edge Apple System Colors unchanged 0% imports direct.ts (1 hops)
agent-pane.composer-edge/no-edge Apple System Colors Light unchanged 0% imports direct.ts (1 hops)
agent-pane.composer-edge/no-edge Apple System Colors unchanged 0% imports direct.ts (1 hops)

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

@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Oct 30, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (gpt-6.1-sol; carried): SHIP at e6bfcd7

Carried from the SHIP at 1ffadb3: e6bfcd7 is a clean merge of feat-cmux-next (e863077, past 4154db0) with no conflicts, and the PR's diff against base is unchanged (identical except line offsets and blob ids). The merge clears the inherited base reds in cmux-next checks (Package dependency lists sorted, Prebuilt web bundles install and stamp, swift-test-summary script test). All three fail at the old base 9129016 and pass at e863077 and at this head. The Sidebar look owner sweep names three base commits (4367c16, 6c1a5ab, eb8105b) and fails the same way at base.

@cursor

cursor Bot commented Oct 30, 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.

teamleaderleo and others added 2 commits October 30, 2026 10:55
When the authoritative peer snapshot omits a peer the status read lists,
its peerStatus becomes only { connected: false }, so the offline banner
(chatUnavailable) loses lastSeen and attempts.

Red: bun test src/agent-session/acpmux/peerStateEvents.test.ts: 10 pass, 1 fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-c35ebd1681cf79649972
The authoritative-snapshot branch replaced an omitted peer's status with
{ connected: false } alone. It now keeps lastSeen, attempts and error
from the last read and drops only the stale state, so the offline banner
still says when the machine was last seen.

Green: bun test src/agent-session/acpmux/peerStateEvents.test.ts: 11 pass, 0 fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-c35ebd1681cf79649972
teamleaderleo and others added 3 commits October 30, 2026 20:37
readPeers writes each _acpmux/status reply into peers, peerConnected,
peerStates and peerStatus before the clock fence runs, so of two
refreshPeers calls the one asked first and answered last wins. The
fixture's status read now answers with what the daemon knew when the read
reached it.

Red: bun test ./src/agent-session/acpmux/peerStateEvents.test.ts: 15 pass, 1 fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-c35ebd1681cf79649972
statusRead now says whether the read is newer than every status read that
already landed (and than this connection); readPeers drops one that is
not before it writes peers, peerConnected, peerStates or peerStatus, so
two refreshPeers calls answered out of order keep the newer answer.

Green: peerStateEvents 16, peerStateReducer 8, chat-unavailable 7,
exportClient 3, chatOpenTrips 3, agent-pane-peer-copy 55,
agent-pane-cached-chat-open 10 pass.

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

Copy link
Copy Markdown
Collaborator Author

Review (gpt-6.1-sol): SHIP at e18a9ca

No findings. Confirmed stale status replies are rejected before map writes, connect’s own read remains valid, reconnect rejects prior reads, and newer snapshots survive racing status replies. Relay changes retain parameter denial and reply filtering.

Static review only; tests were not run.

Diff given to the reviewer: git diff 3e65269f...e18a9ca6 (merge-base). This fixes the independent BLOCK at ede69c26: concurrent refreshPeers replies applied out of order before the clock fence. The red test is committed first (15 pass, 1 fail), then green: peerStateEvents 16, reducer 8, chat-unavailable 7 and exportClient 3 pass. The head also merges feat-cmux-next past 3400861 (the webview base red fix).

@cursor

cursor Bot commented Oct 30, 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.

…r-events-restarting-1030

# Conflicts:
#	cmux-tui/crates/cmux-agent-pane-policy/tests/cases/params.json
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (gpt-6.1-sol): SHIP at 6c92cc2

No verified defects found. The merge resolution preserves _acpmux/native_history, waitLogs, and acp.session.fork.live; _acpmux/peers policy parity and read-generation fencing are intact.

git diff --check and all changed JSON parses passed. Focused fleet tests were unavailable because the broker rejected the cmux-ci request.

Diff given to the reviewer: git diff 51d04cf3...6c92cc22 (merge-base). Since the SHIP at e18a9ca6, the only change is a merge of feat-cmux-next that resolves one conflict in cmux-tui/crates/cmux-agent-pane-policy/tests/cases/params.json. It keeps base's two _acpmux/native_history cases and appends this PR's two _acpmux/peers cases. Locally peerStateEvents 16, reducer 8, chat-unavailable 7, exportClient 3 and chatOpenTrips 3 pass. Web lint's check-design-tokens, check-layers and fmt reds are in webviews/src/pages/settings (this PR doesn't touch it) and fail the same way at base 3f38b7d4.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Carried by #29494 (this lane is gone): same commits and authorship, merged with current feat-cmux-next and its conflicts resolved. This one can close in favour of it.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Awareness note from an agent lane that does not own this PR; it is not a review or a request to take the PR over.


Superseded by merged PR #29494, which contains the local daemon peer state events fix and follow-up corrections.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by #29494.

This branch was previously deployed

3 inactive deployments
artifacts — 6c92cc22 Deployed Oct 31, 2026 by teamleaderleo via add the Windows daemon to the tree #86632
sentry-dsyms — 6c92cc22 Deployed Oct 31, 2026 by teamleaderleo via upload cmux-tui dSYMs to Sentry #86632
ci-routing-agent — 6c92cc22 Deployed Oct 31, 2026 by teamleaderleo via cmux-tui tree preflight #86632
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant