Skip to content

fix(agent-chat): a rejected queue Save hands its text back; Move and Resume catch rejections - #29595

Merged
teamleaderleo merged 6 commits into
feat-cmux-nextfrom
lane/queue-edit-rejection
Nov 1, 2026
Merged

teamleaderleo merged 6 commits into
feat-cmux-nextfrom
lane/queue-edit-rejection

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Agent chat, waiting-prompt queue: the queue's edit calls go through editQueued, which rejects when the bridge fails (not connected, timed out, a host refusal). Three call sites handled only a false answer:

  • Save (an edited prompt): the editor has already closed when the call goes out, and a rejection skipped onLost, so the typed text was neither saved nor handed back, and the row reverted to the old text, with an unhandled rejection logged. Now: if the call failed before acpmux could have it (native.not_connected, a typed host refusal) the row is still queued, so its editor reopens with the typed text (restoring to the composer as well would show the prompt twice); if the row is gone by the time the queue next draws, the open editor hands the text back to the composer as it already does. Each row's latest Save is tracked: an older Save that fails after a newer Save of the same row began is ignored, so it cannot reopen superseded text (that stale editor would autosave the old text over the newer save when clicked away). If several Saves fail in one batch (one bridge reconnect) the first reopens its editor and claims it at once; the others wait, each keeping its own text, and reopen one at a time as each editor closes (a waiting row that has gone hands its text to the composer, and one saved again since is dropped). If acpmux answered no (the prompt already started) the text goes to the composer as before. After a timeout or a closed socket (native.timed_out, transport.closed, transport.stale_connection) the request may already be applied, so nothing is restored or reopened; the queue's next snapshot shows the truth.
  • Move up/down: .finally on the call re-threw the rejection unhandled. Now it is caught.
  • Resume (a paused queue): same unhandled rejection; now caught.

Tests (ComposerQueue.test.tsx): a failed Save reopens the editor with first, reworded for not-connected and a typed refusal and restores nothing to the composer; a reopened editor hands the text back if the row is gone; a timeout or closed socket restores and reopens nothing; an answered-no still restores to the composer; none leaves an unhandled rejection (two macrotask turns, no fixed delay). The reopen tests fail against the previous behavior; bun test src/agent-session/acpmux/ComposerQueue.test.tsx: 35 pass (including overlapping saves with deferred promises: A pending, B saved, A rejects, B succeeds; and two saves failing in one batch), scripts/check-fixed-waits.ts 0 on baseline.

After (Linux gallery, Chromium, at the PR head), the composer with two queued prompts above the field: composer queue

Viewed the after image: the queue renders as before (two rows above the field, steer/queue hint, the controls row); the change is only in what happens when a queue call rejects.

Risk: contained to the three handlers; a call that resolves behaves as before.

Agent session: agent session

…Resume leave no unhandled rejection

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-5de8df9724303a872254
@cursor

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

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 2b8ef43: 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=pr29595 -f ref=lane/queue-edit-rejection -f platform=macos -f backend_mode=local

Gallery before/after for 2b8ef43: 16 states rendered, 11 images differ, 1103 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 41251017956 -R manaflow-ai/cmux -n gallery-advisory-29595-2b8ef43bfbc85d659ac953e1f8e8cb7802a498ca (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.composer/font-size-chat-14 Apple System Colors Light unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-chat-14 Apple System Colors unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-new-chat-14 Apple System Colors Light unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-new-chat-14 Apple System Colors unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-new-tab-14 Apple System Colors Light unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-new-tab-14 Apple System Colors unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-chat-21 Apple System Colors Light unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-chat-21 Apple System Colors unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-new-chat-21 Apple System Colors Light unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-new-chat-21 Apple System Colors unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-new-tab-21 Apple System Colors Light unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-new-tab-21 Apple System Colors unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-chat-28 Apple System Colors Light unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-chat-28 Apple System Colors unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-new-chat-28 Apple System Colors Light unchanged 0% own ComposerQueue.tsx
agent-pane.composer/font-size-new-chat-28 Apple System Colors unchanged 0% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/idle Apple System Colors Light unchanged 0% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/idle Apple System Colors unchanged 0% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/running Apple System Colors Light changed 0.0355% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/running Apple System Colors changed 0.0242% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/subagents-one Apple System Colors changed 0.0413% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/subagents-one Apple System Colors Light changed 0.0268% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/subagents-three Apple System Colors Light changed 0.0622% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/subagents-three Apple System Colors changed 0.0618% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/asking Apple System Colors changed 0.0369% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/asking Apple System Colors Light changed 0.0323% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/queued Apple System Colors changed 0.0761% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/queued Apple System Colors Light changed 0.0562% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/finished Apple System Colors Light unchanged 0% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/finished Apple System Colors unchanged 0% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/cycle Apple System Colors changed 0.388% own ComposerQueue.tsx
agent-pane.composer-turn-cycle/cycle Apple System Colors Light unchanged 0% own ComposerQueue.tsx

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

Passes: CI passes on 2b8ef43bfb.

CI passes on 2b8ef43bfb (run 41251017959 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

Shipped in:

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

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol xhigh, next-review): BLOCK at 6ca0470

BLOCK at 6ca0470

Visual verdict: After/before gallery states and the PR screenshot match; no layout regressions.

  1. [P1][blocking] webviews/src/agent-session/acpmux/ComposerQueue.tsx:664 treats every rejected save as a definitive refusal. A disconnected/timed-out request can already have applied queue_edit before its reply is lost; acpmux rejects pending calls on socket close while the server-side request may continue. onLost then restores the edited text to the composer, and reconnecting shows the edited row too, allowing the prompt to be sent twice. Restore only for definitive refusals or reconcile the row before handing text back.

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

Copy link
Copy Markdown
Collaborator Author

Review (codex-review-box/gpt-6-astra high, lane-place on a fleet lane host; posted by the research overseer because the box host could not write to GitHub): SHIP at 6ca0470

SHIP at 6ca0470

Visual verdict: after screenshot reviewed; no repeated controls, bloated menu, or off-screen layout.

  1. [P1] [follow-up] [ComposerQueue.tsx:660] Every rejected edit restores the typed text, but editQueued can reject after a timed-out or closed bridge request that already reached acpmux. The queue may then update while the composer receives a duplicate prompt, allowing the user to send it twice. Reconcile the row or distinguish uncertain failures before calling onLost.

Follow-ups:

  • [P1] [follow-up] Handle timed-out queue edits as uncertain before restoring text.

…tored; no fixed wait in the test

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

Copy link
Copy Markdown
Collaborator Author

Review (codex-review-box/gpt-6-astra high, lane-place on a fleet lane host; posted by the research overseer because the box host could not write to GitHub): BLOCK at a6536dd

BLOCK at a6536dd

Visual verdict: BLOCK. Opened all 17 after screenshots. The queue follows ChatGPT’s compact composer pattern, but the subagent tray has repeated per-row controls.

  1. [P1] [blocking] webviews/src/agent-session/acpmux/subagents/SubagentTray.tsx:385 — After screenshots 05–10 show a separate Stop button on each running subagent row. This triggers your explicit repeated-control blocking rule. It also appears in the before screenshot; it is an existing visual issue, not introduced by this diff. Move Stop into the selected agent’s detail view or contextual actions.

No regression found in the changed rejection handlers. Tests could not run because Bun and dependencies are absent.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex-review-box/gpt-6-astra high, lane-place on a fleet lane host; posted by the research overseer because the box host could not write to GitHub): BLOCK at 74ca67d

BLOCK at 74ca67d

Visual verdict: BLOCK — inspected all 17 after images against the compact ChatGPT/Claude composer pattern.

  1. [P1] [blocking] webviews/src/agent-session/acpmux/subagents/SubagentTray.tsx:383: Gallery after images 04–09 show repeated per-row Stop controls. These also exist in the before images, but your explicit visual rule makes them blocking. Move the actions into row details or a contextual menu.

No additional defect found in the rejection-handling diff. Tests could not run because Bun is unavailable.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Ruling on the BLOCK at 74ca67d (#29595 (comment)): it does not block this PR.

Its one finding, SubagentTray.tsx:383 (a Stop control on each running row of the tray), is outside this PR's diff. The diff changes only webviews/src/agent-session/acpmux/ComposerQueue.tsx and its test, and nothing under webviews/src/agent-session/acpmux/subagents/. The Stop placement is base's settled design from #29264 ("Running tray: Stop inside its line"). The same review found no defect in the rejection-handling diff. The finding meets none of the blocking criteria (no base break, data loss, security problem or regression introduced here), so it is a follow-up, tracked in cmuxterm-hq#5712. The finding reached this review because the review box takes its after images from every gallery entry that mounts the composer queue. The same ruling was given on #28533.

This PR lands pinned to 74ca67d once its per-test webview verdict (web / react-apps-base-check) is green.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, router-review): BLOCK at 74ca67d

BLOCK at 74ca67d

Visual verdict: All supplied before/after gallery renders match; no visible layout regressions.

  1. [P1][blocking] webviews/src/agent-session/acpmux/ComposerQueue.tsx:676 restores text while the queue row still exists. For an established queue row, native.not_connected or a typed refusal rejects before acpmux receives the edit, leaving the old row unchanged. onLost then routes through startingSends.lost; an ID it does not own is immediately restored to the composer. The user sees both the old queued prompt and the edited text, and sending can duplicate the prompt. Restore only after confirming the row disappeared/started, or retain the edit in the row.

…s the row's editor; only a gone row restores to the composer

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

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol xhigh, router-review): BLOCK at 0e6a0b6

BLOCK at 0e6a0b6

Visual verdict: Reviewed all 16 gallery pairs and the PR screenshot; no visual regression.

  1. [P1] [blocking] ComposerQueue.tsx:684 — An older rejection can overwrite a newer successful Save. Save text A, reopen the same row and save B while A is pending, then let A reject and B succeed. Both editors have closed, so this guard allows A to reopen. B’s successful update leaves that stale editor open; clicking elsewhere autosaves A over B. Base preserves B. Track a per-row save generation so stale failures cannot reopen superseded text, and cover overlapping saves with deferred promises.

Confirmed with a handler-level reproduction against base and head. Both changed files transpile; the Bun suite was unavailable locally.

…an cannot reopen its text

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

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at 6f6fb2b

Reviewer: codex gpt-6.1-sol xhigh, fast tier.

Follow-ups: 1. Each lands as an issue in this repo that links this receipt; the lane that opened the PR fixes it forward.

SHIP at 6f6fb2b

Visual verdict: Reviewed the PR screenshot and all 16 gallery pairs; no new layout regression.

  1. [P2] [follow-up] ComposerQueue.tsx:690 — Two rejected saves can overwrite one recovery draft. If saves to different rows receive typed refusals in one bridge batch, both callbacks read editingNow.current === undefined before React renders. The second overwrites draft.current and the requested editor, leaving the first edit unrecovered. Claim the editor ref synchronously or retain drafts per row. Confirmed by executing the extracted save handler.

Both changed files parse. The Bun suite could not run because Bun and project dependencies are absent.

Follow-ups:

…; drop the void on a void value

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

teamleaderleo commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol xhigh via router-review, diff-scoped; posted by the research overseer because router-review's post timed out on GitHub): SHIP at 2b8ef43

SHIP at 2b8ef43

Visual verdict: before/after gallery pairs match; no repeated controls, bloated menus, or off-screen layouts.

  1. [P1] [follow-up] ComposerQueue.tsx:712-715 does not apply the generation check to resolved false results. If Save A resolves false after Save B begins, A’s superseded text is still sent through onLost, restoring duplicate or stale text to the composer. Gate this branch with latest() and add that deferred-response test.

  2. [P2] [follow-up] ComposerQueue.tsx:171 omits native.failed from uncertain failures. The native bridge can return this when a request’s response is malformed or lost after processing; reopening the edit can therefore duplicate a change that was already applied. Treat it as uncertain like native.timed_out.

  3. [P2] [follow-up] ComposerQueue.tsx:413-414 treats optimistically hidden rows as permanently gone. A pending recovery can be handed to the composer while onRemove/onCancelEdit is still pending; if that action rejects, the row returns and the text is duplicated. Wait for authoritative removal before calling onLost, or cancel the recovery when a removal is pending.

Follow-ups:

  • [P1] Gate resolved-false Save results with the current generation.
  • [P2] Classify native.failed as uncertain.
  • [P2] Avoid restoring recovery text while a row-removal action may still be reversed.

@teamleaderleo
teamleaderleo merged commit 22e3d67 into feat-cmux-next Nov 1, 2026
194 checks passed
@teamleaderleo
teamleaderleo deleted the lane/queue-edit-rejection branch November 1, 2026 00:03

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 2b8ef43b Deployed Oct 31, 2026 by teamleaderleo via cmux-tui tree preflight #91959
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