Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/cli/src/shared/orchestrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,13 @@ async function postInstall(
}
exitCode = await cloud.interactiveSession(sessionCmd);

// SIGINT — exit immediately. The user is mashing Ctrl+C to get out;
// any post-session work (reconnect logic, history pull, tunnel teardown)
// just adds delay that forces yet another Ctrl+C.
if (exitCode === 130) {
process.exit(130);
}

if (!isConnectionDrop(exitCode)) {
break;
}
Expand Down
11 changes: 10 additions & 1 deletion packages/cli/src/shared/ssh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ export function spawnInteractive(args: string[], env?: Record<string, string | u
env: env ?? process.env,
});

const exitCode = result.status ?? 1;

// On SIGINT (Ctrl+C), skip terminal cleanup and return immediately.
// The user is trying to exit — spawning stty sane here blocks just long
// enough that they have to press Ctrl+C a third time to get out.
if (exitCode === 130 || result.signal === "SIGINT") {
return exitCode;
}

// Reset terminal state after the interactive session ends.
// The remote agent's TUI (e.g. Claude Code) may leave the terminal in
// raw mode or with altered attributes, causing garbled post-session output.
Expand All @@ -168,7 +177,7 @@ export function spawnInteractive(args: string[], env?: Record<string, string | u
),
);

return result.status ?? 1;
return exitCode;
}

// ─── Helpers ─────────────────────────────────────────────────────────────────
Expand Down
Loading