Repository navigation
fix(mobile): close terminal session tabs authoritatively - #11240
Conversation
Greptile SummaryThis PR fixes terminal "tab resurrection" bugs (#6927, #7345) on mobile by routing the long-press Close action through the authoritative
Confidence Score: 5/5Safe to merge — the change is narrowly scoped to the terminal long-press Close path and is backed by targeted tests that include a non-vacuity mutation check. The fix is a small, focused routing change: one new callback wired from the session route, one lookup moved earlier, and a two-branch conditional in the Close handler. All three branches (authoritative owner, split-leaf owner, and unmatched fallback) are covered by the new tests, and the PR description confirms that reverting the routing branch breaks two of the four focused tests. The Files Needing Attention: No files require special attention.
|
| Filename | Overview |
|---|---|
| mobile/src/session/mobile-terminal-action-sheet-actions.ts | Adds onCloseSessionTab callback, moves sessionTab lookup before the action list, and routes Close through the session-tab owner when found; clean and correct. |
| mobile/app/h/[hostId]/session/[worktreeId].tsx | Single-line wiring of onCloseSessionTab to the existing handleCloseSessionTab function; minimal, correct change. |
| mobile/src/session/mobile-terminal-action-sheet-actions.test.ts | Introduces buildActions helper and three new focused tests; covers authoritative path, split-leaf ownership, and fallback; no issues. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User long-presses terminal\nand taps Close] --> B[getMobileTerminalActionSheetActions\nonPress handler]
B --> C[onDismiss: close action sheet]
C --> D{sessionTab found?\ntabs.find tab.terminal === handle}
D -- Yes --> E[onCloseSessionTab tab\nsession.tabs.close RPC]
E --> F[Prune sessionTabs\nRecord tombstone\nClear activeSessionTabId\nUnsubscribe terminal handle]
D -- No --> G[onClose target\nterminal.close RPC]
G --> H[Remove from local terminals list\nClear activeHandle]
Reviews (1): Last reviewed commit: "fix(mobile): close terminal session tabs..." | Re-trigger Greptile
📝 WalkthroughWalkthroughThe terminal action sheet now accepts typed session tabs and an 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Root cause: terminal long-press Close alone used handle-only |
Summary
Fixes #6927
Fixes #7345
Merge ordering
Depends on #11251 (
brennanb2025/mobile-9717-dup-sessions, issue #9717) — land #11251 first.This PR correctly routes more terminal closes through the authoritative
session.tabs.closepath, which prunessessionTabsand clearsactiveSessionTabId. That legitimately reaches the empty-session-tab-list statefar more often, and on
mainthat state can trigger an unsolicited replacementterminal — the duplicate-session bug in #9717. Landing this PR first therefore
makes #9717 more reachable without introducing it.
#11251 fixes the auto-create side (
use-initial-session-terminal-autocreate.ts)by treating an authoritative empty snapshot as a real empty workspace. The two
branches combine cleanly and do not require an atomic merge, but this PR should
not land before #11251. #11251 records the same ordering constraint.
Root cause
Terminal long-press Close was the only mobile tab close that called handle-only
terminal.close. That removed the PTY-facing terminal but did not prune the mobilesessionTabs, clearactiveSessionTabId, or record the local close tombstone. The host could therefore republish the same active tab as pending, after which the pending-terminal activation effect materialized a fresh PTY into the same tab id.#7345 reports this exact terminal resurrection path. #6927 sees it as every tab being unclosable because a headless
orca servesession normally publishes terminal tabs; #10486 is separate because its trigger is viewing/activation and does not exercise close.Reliability contract
terminal-session.explicit-close-retirement): an explicit mobile terminal-tab close uses the session-tab owner transaction, removes the local mirror, clears active ownership, and records the tombstone; an unmatched live handle retains the existing fallback.terminal-session.explicit-close-retirementis partial; this PR adds the mobile route-level oracle and records live-device validation as a gap.RuntimeMobileSessionTabCloseResult.refused, butsession.tabs.closehardcodes user intent and cannot currently reach a refusal branch. Handling a future user-close refusal is intentionally out of this P1 scope.Verification
Against parent
d3681f6306:cd mobile && pnpm installusing the repository-pinned pnpm 10.24.0--maxWorkers=2oxfmt --check .tsc --noEmitNo device validation is claimed.
CI note (pre-existing unrelated flake)
tests 12/16failed once on this commit. All 233 shard files and 2,247 testspassed; the job exited 1 solely on
Unhandled Rejection: Error: Unable to remove watcher: Invalid argument, thrown from@parcel/watcher's Linuxinotify backend (
inotify_rm_watch->EINVAL).Not caused by this PR:
includematches no file under the repo-rootmobile/directory, so this mobile-only diff leaves shard 12's file setbyte-identical to the parent — it cannot shift shard contents.
src/main/plugins/plugin-content-safety.test.ts) contains nowatcher code; Vitest attributes an async native error to whichever test was
in flight.
The latent hazard is in
src/main/ipc/parcel-watcher-in-process-fallback.ts:the cancellation path deliberately swallows unsubscribe failures ("so they never
surface as unhandled rejections"), but the normal teardown path awaits
subscription.unsubscribe()unguarded. Desktop main-process code, unrelated tothis mobile fix, and intentionally left out of scope here.