Skip to content

Commit dd03e08

Browse files
committed
fix(tui): avoided OSC 11 polling on Windows Subsystem for Linux
- Added WSL detection in terminal initialization and skipped periodic OSC 11 polling when running under Windows Subsystem for Linux. - Removed mode 2031 activity tracking and stopped OSC 11 polling immediately when DA1/Mode 2031 reports were received. - Extended OSC 11 appearance tests to restore platform/env state and verify no polling is started under WSL. Fixes #914
1 parent f224326 commit dd03e08

4 files changed

Lines changed: 43 additions & 10 deletions

File tree

packages/coding-agent/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# Changelog
22

33
## [Unreleased]
4+
### Added
5+
6+
- Added `statusLine.sessionAccent` to disable session-name accent coloring for the editor border and status line gap ([#918](https://github.com/can1357/oh-my-pi/issues/918))
7+
48
### Fixed
59

10+
- Disabled repeated OSC 11 background-color polling under WSL to avoid Windows terminal tab crashes while keeping initial and event-driven appearance detection ([#914](https://github.com/can1357/oh-my-pi/issues/914))
11+
612
- Fixed SSH ControlMaster socket paths to use OpenSSH's connection hash (`%C`) so connections to the same host with different users, ports, or jump hosts do not share a master session.
713

814

packages/coding-agent/src/modes/interactive-mode.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,9 @@ export class InteractiveMode implements InteractiveModeContext {
644644
} else if (this.isPythonMode) {
645645
this.editor.borderColor = theme.getPythonModeBorderColor();
646646
} else {
647-
const sessionName = settings.get("statusLine.sessionAccent") ? this.sessionManager.getSessionName() : undefined;
647+
const sessionName = settings.get("statusLine.sessionAccent")
648+
? this.sessionManager.getSessionName()
649+
: undefined;
648650
const hex = sessionName ? getSessionAccentHex(sessionName) : undefined;
649651
const ansi = getSessionAccentAnsi(hex);
650652
if (ansi) {

packages/tui/src/terminal.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ export interface Terminal {
9696
get appearance(): TerminalAppearance | undefined;
9797
}
9898

99+
function isWindowsSubsystemForLinux(): boolean {
100+
return process.platform === "linux" && (!!$env.WSL_DISTRO_NAME || !!$env.WSL_INTEROP);
101+
}
102+
99103
/**
100104
* Real terminal using process.stdin/stdout
101105
*/
@@ -105,7 +109,7 @@ export class ProcessTerminal implements Terminal {
105109
#resizeHandler?: () => void;
106110
#kittyProtocolActive = false;
107111
#modifyOtherKeysActive = false;
108-
#modifyOtherKeysTimeout?: ReturnType<typeof setTimeout>;
112+
#modifyOtherKeysTimeout?: Timer;
109113
#stdinBuffer?: StdinBuffer;
110114
#stdinDataHandler?: (data: string) => void;
111115
#dead = false;
@@ -118,7 +122,6 @@ export class ProcessTerminal implements Terminal {
118122
#osc11ResponseBuffer = "";
119123
#pendingDa1Sentinels = 0;
120124
#osc11PollTimer?: Timer;
121-
#mode2031Active = false;
122125
#mode2031DebounceTimer?: Timer;
123126

124127
get kittyProtocolActive(): boolean {
@@ -185,7 +188,12 @@ export class ProcessTerminal implements Terminal {
185188

186189
// Start periodic OSC 11 re-query for terminals without Mode 2031
187190
// (Warp, Alacritty, WezTerm, iTerm2). Self-disables once Mode 2031 fires.
188-
this.#startOsc11Poll();
191+
// Windows Terminal under WSL has been observed to close the hosting tab
192+
// after repeated OSC 11/DA1 probes. Keep the initial/event-driven probes,
193+
// but avoid background polling there.
194+
if (!isWindowsSubsystemForLinux()) {
195+
this.#startOsc11Poll();
196+
}
189197
}
190198

191199
/**
@@ -328,10 +336,7 @@ export class ProcessTerminal implements Terminal {
328336
// (Neovim convention — coalesces rapid notifications during transitions)
329337
const appearanceMatch = sequence.match(appearanceDsrPattern);
330338
if (appearanceMatch) {
331-
if (!this.#mode2031Active) {
332-
this.#mode2031Active = true;
333-
this.#stopOsc11Poll();
334-
}
339+
this.#stopOsc11Poll();
335340
if (this.#mode2031DebounceTimer) clearTimeout(this.#mode2031DebounceTimer);
336341
this.#mode2031DebounceTimer = setTimeout(() => {
337342
this.#mode2031DebounceTimer = undefined;
@@ -515,7 +520,6 @@ export class ProcessTerminal implements Terminal {
515520
this.#osc11QueryQueued = false;
516521
this.#osc11ResponseBuffer = "";
517522
this.#pendingDa1Sentinels = 0;
518-
this.#mode2031Active = false;
519523

520524
// Disable Kitty keyboard protocol if not already done by drainInput()
521525
if (this.#kittyProtocolActive) {

packages/tui/test/terminal-appearance.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ProcessTerminal } from "@oh-my-pi/pi-tui/terminal";
33

44
const stdinIsTtyDescriptor = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");
55
const stdoutIsTtyDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
6+
const processPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
67
const stdinSetRawModeDescriptor = Object.getOwnPropertyDescriptor(process.stdin, "setRawMode");
78

89
function restoreProperty(target: object, key: string, descriptor: PropertyDescriptor | undefined): void {
@@ -26,6 +27,9 @@ describe("ProcessTerminal OSC 11 appearance detection", () => {
2627
restoreProperty(process.stdin, "isTTY", stdinIsTtyDescriptor);
2728
restoreProperty(process.stdout, "isTTY", stdoutIsTtyDescriptor);
2829
restoreProperty(process.stdin, "setRawMode", stdinSetRawModeDescriptor);
30+
restoreProperty(process, "platform", processPlatformDescriptor);
31+
delete Bun.env.WSL_INTEROP;
32+
delete Bun.env.WSL_DISTRO_NAME;
2933
});
3034

3135
function setupTerminal() {
@@ -152,7 +156,7 @@ describe("ProcessTerminal OSC 11 appearance detection", () => {
152156
terminal.stop();
153157
});
154158

155-
it("poll timer self-disables when Mode 2031 fires", () => {
159+
it("poll timer self-disables when Mode 2031 fires outside WSL", () => {
156160
vi.useFakeTimers();
157161
const { terminal, queryCount } = setupTerminal();
158162

@@ -186,6 +190,23 @@ describe("ProcessTerminal OSC 11 appearance detection", () => {
186190
terminal.stop();
187191
});
188192

193+
it("does not start the OSC 11 poll timer under WSL", () => {
194+
vi.useFakeTimers();
195+
Object.defineProperty(process, "platform", { value: "linux", configurable: true });
196+
Bun.env.WSL_INTEROP = "/run/WSL/1_interop";
197+
const { terminal, queryCount } = setupTerminal();
198+
199+
process.stdin.emit("data", "\x1b]11;rgb:ffff/ffff/ffff\x07");
200+
process.stdin.emit("data", "\x1b[?1;2c");
201+
const afterInitial = queryCount();
202+
203+
vi.advanceTimersByTime(4000);
204+
205+
expect(queryCount()).toBe(afterInitial);
206+
207+
terminal.stop();
208+
});
209+
189210
it("partial OSC 11 buffer does not swallow unrelated input", () => {
190211
vi.useFakeTimers();
191212
const { terminal, received } = setupTerminal();

0 commit comments

Comments
 (0)