diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 1a3af79d..59be26f3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Kitty key release events leaking to parent shell over slow SSH connections ([#1204](https://github.com/badlogic/pi-mono/issues/1204)) + ## [0.51.1] - 2026-02-02 ### New Features diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 300a7516..3f852185 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -2552,6 +2552,10 @@ export class InteractiveMode { // requestRender() uses process.nextTick(), so we wait one tick await new Promise((resolve) => process.nextTick(resolve)); + // Drain any in-flight Kitty key release events before stopping. + // This prevents escape sequences from leaking to the parent shell over slow SSH. + await this.ui.terminal.prepareForExit(); + this.stop(); process.exit(0); } diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index c08fc4b9..b94819a8 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +### Added + +- Added `Terminal.prepareForExit()` method to drain Kitty key release events before exit + +### Fixed + +- Fixed Kitty key release events leaking to parent shell over slow SSH connections ([#1204](https://github.com/badlogic/pi-mono/issues/1204)) + ## [0.51.1] - 2026-02-02 ### Added diff --git a/packages/tui/src/terminal.ts b/packages/tui/src/terminal.ts index 9c852b3e..985afb08 100644 --- a/packages/tui/src/terminal.ts +++ b/packages/tui/src/terminal.ts @@ -12,6 +12,14 @@ export interface Terminal { // Stop the terminal and restore state stop(): void; + /** + * Prepare for process exit by disabling Kitty protocol and draining stdin. + * Call this before stop() when exiting to prevent Kitty key release events + * from leaking to the parent shell over slow SSH connections. + * @param drainMs - How long to drain stdin (default: 50ms) + */ + prepareForExit(drainMs?: number): Promise; + // Write output to terminal write(data: string): void; @@ -150,11 +158,25 @@ export class ProcessTerminal implements Terminal { process.stdout.write("\x1b[?u"); } + async prepareForExit(drainMs = 50): Promise { + if (!this._kittyProtocolActive) return; + + // Disable Kitty keyboard protocol first + process.stdout.write("\x1b[ setTimeout(resolve, drainMs)); + } + stop(): void { // Disable bracketed paste mode process.stdout.write("\x1b[?2004l"); - // Disable Kitty keyboard protocol (pop the flags we pushed) - only if we enabled it + // Disable Kitty keyboard protocol if not already done by prepareForExit() if (this._kittyProtocolActive) { process.stdout.write("\x1b[ { + // No-op for virtual terminal - no Kitty protocol to drain + } + stop(): void { // Disable bracketed paste mode this.xterm.write("\x1b[?2004l");