Skip to content

Fix native chat close and stale tab reopening - #27583

Draft
brennanb2025 wants to merge 3 commits into
mainfrom
brennanb2025/tab-close-chat-host-close
Draft

brennanb2025 wants to merge 3 commits into
mainfrom
brennanb2025/tab-close-chat-host-close

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 21 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​960 $\color{#cf222e}{\Huge{\mathbf{−}}}$​62 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​898
Prod 29 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​986 $\color{#cf222e}{\Huge{\mathbf{−}}}$​280 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​706

ELI5

Closing a native chat could hide its tab while the agent kept running. An older create, reopen, or startup restore could bring the tab back, and closing a saved conversation could fail before its owner was loaded.

The computer running the chat now handles one close operation. It cancels older attempts to show the chat, hides the tab, and attempts to stop execution even if saving visibility or updating the listing fails. History is preserved. An explicit reopen waits at most three seconds for an unfinished close, then returns the existing retry response if necessary.

What Changed

  • Track creates, reopens, and startup restores until they finish showing their tabs. A canceled attempt cannot bring a closed chat back into live tabs or the saved layout on a computer with no app window. Startup preserves untouched legacy conversations.
  • Identify the conversation behind a custom tab before waiting, then check that the tab was not reassigned. A saved chat can load its owner without waiting for a terminal scan, including over SSH.
  • Preserve confirmation and veto in other windows and for command-line, mobile, paired, and untrusted callers. Only the actual originating desktop window skips its duplicate confirmation; clients cannot supply that trusted identity.

This PR stands on main after #27565 closed. Terminal, browser, and editor closing follows main; this diff concerns native chats. The renderer switch to the single close command remains #27586. Existing clients remain compatible through session-tabs.close-agent-session-by-id.v1.

Why

The execution host already owns the agent and conversation. Completing close there covers every caller; ordering commands in one renderer would leave other callers exposed. Cancellation belongs to pending operations rather than a permanent closed flag, and bounded waiting lets users retry when close never settles.

Intended differences from the common pattern: closing preserves history, a pending action can start before a saved owner is loaded, and trusted local window routing supports the existing confirmation flow. The desktop command change remains temporary until #27586 lands.

Linked Issue

Part of the tab-close lifecycle series beginning with #27561. No separate issue.

Visual Proof

Production QA captures on 9ec374103db, before and after each action on that same commit. Current 0c9b3ee84cd changes only a test expectation; the production code is identical. Hidden renderers; no desktop activation.

M4Air macOS, real Claude claude-opus-5-5[1m]. Newly generated assistant text grew before close. The original process was gone at the post-close check, and 40 observations over 59 seconds showed no returning chat tab.

Answering before close After close
Answering before close After close

The real History Resume was clicked 1.4 seconds after starting close, with no history refresh between. It reopened the same conversation once with retained assistant text; 50 observations over 75 seconds showed no duplicate or later loss. The original Claude process was gone before Resume.

Answering before close Same chat after immediate Resume
Answering before close Same chat after immediate Resume

SSH control on a disposable Ubuntu 20.04 host: the identified busy terminal's real X and “Stop and Close” removed its sleep 600 child before its natural deadline, and the next full host list was empty.

Busy SSH terminal before close After Stop and Close
Busy SSH terminal before close After Stop and Close

Testing

  • M4Air real-Claude close and immediate same-chat History Resume on the unchanged production code.
  • SSH busy-terminal close and genuine empty-chat close/direct host reveal on that production code.
  • Automated lifecycle, startup, window routing, older-client and permission-seed contracts.
  • Required CI aggregate passed; it is blocked by two errors in a test from newer main.

Current head is 0c9b3ee84cdafd71077fcea477151d70c0fbe7d0. Its only change after QA head 9ec374103db updates the existing permission-create expectation to require the intended third attach argument containing an AbortSignal. The permission seed and two-argument ensure expectation are preserved. No production code changed. The scan of matching attach/create assertions found no additional proven mismatch.

262 tests across 24 explicit files passed: the original 23-suite composition plus the permission-create suite. The three existing windowless-layout suites remain unchanged. The combined restore/close regression checks saved and live tabs, preserves the untouched sibling, and reopens once. Normal Node compilation passed. Prepush completed 21/22 checks; the only failure is the documented local Bun 1.3 compiler wrapper incompatibility, rather than a source diagnostic.

Counting fixtures and test configuration as tests, the own diff against pinned main f31aada5 is tests +966/-66 versus production +980/-276 (1,032 versus 1,256 changed lines). No lockfile or documentation changes are included.

The one new-head fail-fast watch stopped on static analysis, at “Reject low-evidence patterns.” Both errors are in mobile/src/tasks/use-mobile-jira-connection-state.test.ts: the client argument at line 16 and nextClient at line 34 use the broad object type.

This is confirmed newer-main code: CI tested merge 2ab9e7b7, combining this PR with main 0c05e6d3. The failing file is absent from this PR's head and byte-identical between the CI merge and that main parent (blob 286bae38a23fb8e2122fd2bf95b623288674db7c); it was introduced by #28616. The exact CI diagnostics and both immutable sources were read and compared. No separate main lint run is claimed. Local prepush used this branch, where that file does not exist.

The fail-fast snapshot had six passing checks, one failure, one queued aggregate and 24 skipped checks. Repository guards, Mobile verify, line count, and all three native smoke checks passed. Unit tests never ran in this CI attempt because static analysis failed; neither a new CI compiler pass nor an aggregate pass is claimed. The compiler step's success status was a guarded no-op. Local compilation did run and pass.

Historical CI inventory, superseding the earlier incomplete report:

  • This PR's failure, fixed at 0c9b3ee84cd: the permission-create assertion omitted the intended cancellation argument. It passed on main and failed on 9ec; both create/ensure cases and the full focused set now pass locally.
  • Confirmed on pinned main f31: headless-detector-compiler-cache.test.mjs:199 reads workflow.on.push.paths after ci: stop daily and post-merge headless-server checks #28664 removed the push trigger. An exact-file control reproduced it; test/workflow contents match main.
  • Two additional failures confirmed on pinned main f31: orca-runtime-files-artifact-facade.test.ts:125 and :163 expect three arguments for openMobileFile, while the unchanged main owner takes four. The coordinator ran both main controls, and root verified the test/owner/facade byte identity.
  • Prior browser timeout, cause unresolved: the standalone route/opaque native-control probe timed out. Its unchanged bundle contains none of this PR's changed production modules. A main browser reproduction and flakiness remain unproven. It did not run in the new CI attempt.

The requested assertion correction is complete. Required CI remains blocked by the newer-main audit errors above. No further main merge, CI rerun, replacement watch or unrelated source change was made.

Review

Focused adversarial review of the standalone diff found no proven P0/P1/P2 issue. It covered removing the closed parent, SSH, folder workspaces, startup membership, and older clients. The main integration also passed the combined regression and final-head QA above.

Agent skill upstream boundary

  • Not applicable; no upstream skill-installer implementation copied.

Notes

#28354 has landed on main; its saved-layout write now runs inside the same single !input.signal?.aborted check as showing the tab.

Busy SSH chat remains unverified: no installed, signed-in agent was used on that fixture. The SSH chat case created a genuine empty conversation without a first message, closed it through X, then used direct host reveal for the same session; it does not establish SSH History Resume or retained nonempty messages. Live Resume occurred after close completed; held-operation tests cover cancellation races. Setup scripts were not run in the UI scenarios, and large-history latency was not measured. If saving visibility fails, keeping the tab closed across a later restart remains best effort.

Owned QA apps, helpers, containers, temporary keys, isolated profiles, controller configuration, and the M4Air qa-pr-27583 worktree are removed. Raw evidence is preserved outside the checkouts. This PR remains draft; required CI is blocked by the newer-main audit errors above.

Checklist

  • Focused native-chat lifecycle scope; problem, behavior, mechanism, and alternatives explained.
  • Fresh before/after visual proof attached.
  • Correctness, security, performance, and compatibility source review completed.
  • Owned QA resources cleaned after preserving evidence.
  • Final required CI checks and aggregate passed.

@brennanb2025

brennanb2025 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Closing a native chat could hide its tab while the agent continued running; an older create, reopen or startup restore could bring it back. The execution host now owns one close operation, cancels older publication attempts, and attempts stop despite bookkeeping failures. History is preserved, and reopening waits at most three seconds for an unfinished close.

The PR stands on main after #27565 closed. Main's landed #28354 saved-layout write shares the cancellation guard with live tab publication; startup reconciliation stays inside the tracked restore lifetime. Other tab kinds follow main. Existing-client compatibility is preserved; the renderer command switch remains #27586.

Requested correction complete at 0c9b3ee84cdafd71077fcea477151d70c0fbe7d0. The stale permission-create assertion now requires the intended third attach argument containing the cancellation signal; ensure still expects two arguments and permission seed assertions remain intact. This PR-caused failure was missed by the earlier first-failing-shard report. The related assertion scan found no additional proven mismatch. Only this test changed after QA at 9ec; production code is identical and no new QA run is claimed.

262 focused tests across 24 explicit files and normal Node compilation passed. Tests/fixtures/configuration: +966/-66 versus production +980/-276 against pinned mainf31. Prepush passed 21/22, with only the documented local Bun wrapper limitation. The focused review is complete.

The ONE new-head watch exited on static analysis. Both audit errors are from newer main: mobile/src/tasks/use-mobile-jira-connection-state.test.ts:16 uses client: object, and line34 uses nextClient: object. The file is absent from this PR head. Root read the exact CI source and main0c05 parent source and verified their identical blob286bae38a23fb8e2122fd2bf95b623288674db7c; CI checked merge2ab9e7b7, and #28616 introduced the file. No separate main lint run is claimed. The snapshot had6passed/1failed/1queued/24skipped; guards, Mobile, LoC and all3native smoke checks passed. Unit tests were skipped because static failed. The CI compiler step was a guarded no-op, so only the actual local compiler pass is claimed. Required CI is NOT GREEN.

The complete historical inventory also includes the mainf31 compiler-cache workflow mismatch and TWO mainf31 openMobileFile arity failures (test expects3; owner has4). The coordinator reproduced these on main; root read actual source/blob evidence and an independent compiler-cache control. The prior standalone browser route/opaque timeout remains unexplained: no changed production modules are in its bundle, but a main browser reproduction and flakiness are unproven. None of those cases ran in this new CI. No further main merge, unrelated fix, rerun or replacement watch followed.

M4Air production QA at 9ec374103db used real Claude claude-opus-5-5[1m] and proved new assistant text growing before X. The original child process disappeared before real History Resume, clicked 1.4 seconds after starting close; the same conversation returned once with retained text and stayed stable across 50 observations over 75 seconds. A separate close-only row kept the tab absent across 40 observations over 59 seconds. The live Resume occurred after close completed; held-operation tests cover cancellation races.

Fresh SSH QA used an owned Ubuntu 20.04 host. The busy terminal's real X and “Stop and Close” removed its child before its natural deadline and left the next host list empty. A genuine empty chat was created without a first message, closed through its rendered X, and directly revealed as the same session. Busy SSH chat remains unverified; this does not establish SSH History Resume or retained nonempty messages. Setup scripts and large-history latency were not exercised in these UI scenarios.

Uncropped fresh proof is in the PR body. Owned QA processes, listeners, containers, keys, isolated profiles, controller configuration, and the M4Air qa-pr-27583 worktree are removed; raw evidence is preserved outside the checkouts. This PR remains draft. The requested correction is complete; required CI is blocked by newer-main audit errors.

@brennanb2025
brennanb2025 force-pushed the brennanb2025/tab-close-chat-host-close branch from ce3ba42 to ec36bdb Compare October 14, 2026 08:22
@brennanb2025 brennanb2025 changed the title A chat close is one host operation that always stops the agent Fix native chat close and stale tab reopening Oct 14, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant