Web Shell sidebar and standalone session actions
Contribution roleIssue claimant, fix author and verifier — addressed an upstream Critical and follow-up review suggestions; new-head CI and re-review are running
Leaving the current standalone session before deleting it
The Web Shell disabled Delete for the current standalone session because the daemon refuses to remove an attached session. My fix enables deletion only when idle, waits for a verified detach after confirmation, and then deletes the original ID. A busy session in another tab stays protected. An upstream review found that the dialog could close while deletion was still running; I fixed that race and verified it with paused real-daemon requests. Follow-up review suggestions are now pushed as well; the PR stays open while CI and re-review run on the new head.
- Issue
- #12669
- Pull request
- #12738
- Commits in this contribution
- e226eb362dLeave the current standalone session before deletioncf7377b6efAdd end-to-end verification screenshots and notes588cb890fdKeep the confirmation dialog locked during deletiona2b39eb405Address review suggestions: deletion progress, readiness and regression tests
- Module
- Web Shell sidebar and standalone session actions
System context
Web Shell lists standalone sessions in its sidebar. The daemon refuses to delete a session with an active prompt or attached client; its batch deletion route reports a per-session session_busy error inside an HTTP 200 response.
The earlier #12636 change disabled Delete for the current row. Its maintainer left the alternative of leaving first and then deleting for follow-up Issue #12669. The current tab can detach, but another tab may still be attached to the same session.
Failure mode and impact
On the unmodified source baseline, the current standalone row displayed Delete but kept it disabled. Simply enabling the button would send deletion while this tab was still attached and receive session_busy from the daemon.
A normal New task result did not prove that detach succeeded: clearSession swallowed detach errors, and the SDK treated a missing clientId as a no-op. Sidebar list counts could also lag behind the live state.
Upstream review of the first PR version found a separate Critical: while asynchronous detach or deletion ran, Cancel and other close controls could dismiss the confirmation dialog even though deletion would continue.
Cause
The client lacked an ordered operation for the current row: confirm deletion, leave the exact original session, prove that detach finished, then request deletion of that fixed ID. The daemon's busy guard was correct and must remain in place.
The existing callback discarded the result of New task, while the ordinary clearSession path could hide detach failure. Neither was suitable as the prerequisite for a destructive delete request.
Fix
Both sidebar Delete entrances now allow an idle current standalone row to reach the existing confirmation dialog. The action rechecks the live App state and original session ID at confirmation; running work remains protected.
A deletion-only awaitable callback reuses createNewSession({ kind: 'global' }) with strict clearSession checks. It requires the expected session ID and matching nonempty clientId values, reports detach failure, and waits for detach before sending batch deletion for the fixed original ID.
Only removed or notFound removes the row. A per-item session_busy or request failure leaves it visible and reports the error; after leaving, the list is refreshed. The daemon busy guard and the separate /delete selector remain unchanged.
After upstream review, I disabled every dialog close entrance during the operation and added a synchronous busy guard to onClose. The confirmation remains visible while detach or deletion is pending.
Following the review's suggestions, commit a2b39eb405 shows progress while deletion runs (Deleting... with aria-busy), keeps Delete disabled until the session is attached and ready, gives one message for a failed leave or a cancelled navigation, removes a dead branch around an optional callback, and adds regression tests for retry, refresh and strict detach. The English and Chinese design notes were updated to match.
Design trade-offs
Leaving starts only after the user confirms, so cancelling the dialog cannot detach a session. The operation is locked to one original ID, and deletion is never sent while strict detach is unfinished or failed.
The UI does not block deletion based on a potentially stale clientCount. The daemon decides whether another tab makes the session busy; no force-delete path or unconditional retry weakens that protection.
A confirmed deletion must not appear cancelled while its asynchronous work continues. The review-driven busy guard closes that UI race without changing the daemon's deletion rules.
Verification
- 01
Before the initial fix, a focused baseline component test confirmed that Delete was disabled on the current row. The first implementation passed 1,570 tests across four targeted files, repository-wide typecheck and build. An initial local code review missed the dialog race later identified upstream.
- 02
An isolated real daemon and built Web Shell in Chrome confirmed the single-tab order: cancelling sent neither request; confirming produced detach HTTP 204 before batch delete HTTP 200 with the original ID in removed, and the row disappeared.
- 03
With two Chrome contexts, the first tab detached (clientCount 2 to 1), then batch deletion returned HTTP 200 with session_busy in errors[]; the original row remained and the page showed an error. After the second tab detached (1 to 0), retry from the retained dialog removed the row. Failure injection for detach and an active prompt was covered by targeted tests, not this live run.
- 04
For the upstream Critical, the old implementation reproduced a misleading Cancel while a paused real-daemon detach still proceeded to deletion. Commit 588cb890fd made the targeted regression fail before the fix and pass after it. With detach and delete requests paused in a real daemon and Chrome, Cancel, Esc, backdrop and the close button could no longer dismiss the dialog. After merging newer upstream main without rewriting history, repository-wide build and typecheck and 50 relevant component tests passed at a179cd17af.
- 05
For a2b39eb405, four targeted Web Shell test files passed 1,572/1,572 and six affected sidebar files 87/87; repository-wide build and typecheck, lint and format on the changed files, the whitespace check and commit hooks passed. With an isolated real daemon and Chrome, a paused request showed Deleting... with aria-busy, Delete stayed disabled while the session loaded, and a detach HTTP 503 injected in the browser kept the row without sending a delete request. That 503 was injected in the browser; it is not a real daemon transport failure.
- 06
At 2026-09-27 00:49 UTC, PR #12738 remained OPEN and MERGEABLE at a2b39eb405, with reviewDecision CHANGES_REQUESTED and mergeStateStatus BLOCKED. CI on the new head was still running: 9 checks passed, 7 were skipped and 5 were pending, with no failures so far; the previous head's finished checks do not carry over. The reviewer had confirmed the Critical fixed, but the latest submitted review was COMMENTED, not a new approval. 13 review discussions remained unresolved, 4 of them now marked outdated by the new commit. A supplementary end-to-end report was posted on the PR. Full preflight, other operating systems and a fresh frozen-dependency install were not run locally.
- 07
An earlier successful visual check reported no screenshot difference against the base, so it did not verify the new Delete flow. The browser interaction evidence above came from the isolated local daemon run.
Upstream progress
- Issue claim
Claim comment posted; no formal maintainer assignment or patch approval
- Fix and local verification
Initial 1,570 targeted tests passed; Critical fixed with red-green regression, 50 relevant tests and paused real-daemon checks; follow-up commit passed 1,572 targeted and 87 sidebar tests
- Pull request
#12738 opened with Fixes #12669; end-to-end report and a supplementary report posted separately
- Upstream review
Critical confirmed fixed and suggestions pushed, but CHANGES_REQUESTED remains; latest review COMMENTED, no renewed approval
- Upstream CI
New head a2b39eb405: 9 passed, 7 skipped, 5 pending, none failed so far
- Merge
Not merged; MERGEABLE means no conflict, while mergeStateStatus remains BLOCKED