Compare commits

...
Author SHA1 Message Date
Cursor Agentand陈大猫 3428e36931 fix(terminal): keep home input after an attached popup closes
Attach close preparation clears the popup session ref, then unmount
cleanup called closeSession. That emits netcatty:exit to the home
renderer and can kill the shared PTY. Skip that second close.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-10-22 15:42:08 +00:00
2 changed files with 18 additions and 0 deletions

No files matched your search

+8
View File
@@ -1794,6 +1794,14 @@ const TerminalComponent: React.FC<TerminalProps> = ({
if (!closingSessionId) {
disposeSessionListeners();
// Close preparation already cleared sessionRef after the display handoff.
// Unmount teardown calls cleanup again. An attach popup must not
// closeSession on that second pass: worker mode emits netcatty:exit to
// the home renderer before the boot-epoch check, which drops home input
// (sessionRef = null) while a mismatched epoch leaves the PTY alive.
if (attachExistingSession) {
return;
}
// Still notify main so in-flight SSH passphrase prompts for this UI
// sessionId are aborted even before a backend session was attached.
try {
+10
View File
@@ -652,6 +652,16 @@ test("terminal boot is cancelable and closes eagerly on cleanup", () => {
);
// Owner panes close the pending backend; attach popups only dispose xterm.
assert.match(neverConnectedBranch, /if \(!attachExistingSession\)/);
const emptySessionBranch = readFileSync(new URL("../Terminal.tsx", import.meta.url), "utf8");
const emptyStart = emptySessionBranch.indexOf("if (!closingSessionId)");
const emptyEnd = emptySessionBranch.indexOf("Observe/attach popups must not kill", emptyStart);
const emptyBranch = emptySessionBranch.slice(emptyStart, emptyEnd);
const attachGuardAt = emptyBranch.indexOf("if (attachExistingSession)");
const emptyCloseAt = emptyBranch.indexOf("terminalBackend.closeSession(");
assert.ok(
attachGuardAt !== -1 && emptyCloseAt !== -1 && attachGuardAt < emptyCloseAt,
"a second attach cleanup after sessionRef is cleared must not closeSession",
);
assert.match(effectsSource, /let ownedRuntime:/);
assert.match(
cleanup,