fix: prevent /copy crash on Windows by skipping /dev/tty (#15657)

Co-authored-by: Jack Wotherspoon <jackwoth@google.com>
This commit is contained in:
Manoj Naik
2026-01-06 02:03:03 +05:30
committed by GitHub
parent e5d183031a
commit 2da911e4a0
2 changed files with 44 additions and 6 deletions
@@ -98,6 +98,9 @@ describe('commandUtils', () => {
beforeEach(async () => { beforeEach(async () => {
vi.clearAllMocks(); vi.clearAllMocks();
// Reset platform to default for test isolation
mockProcess.platform = 'darwin';
// Dynamically import and set up spawn mock // Dynamically import and set up spawn mock
const { spawn } = await import('node:child_process'); const { spawn } = await import('node:child_process');
mockSpawn = spawn as Mock; mockSpawn = spawn as Mock;
@@ -354,6 +357,35 @@ describe('commandUtils', () => {
expect(tty.write).not.toHaveBeenCalled(); expect(tty.write).not.toHaveBeenCalled();
expect(tty.end).not.toHaveBeenCalled(); expect(tty.end).not.toHaveBeenCalled();
}); });
it('skips /dev/tty on Windows and uses stderr fallback for OSC-52', async () => {
mockProcess.platform = 'win32';
const stderrStream = makeWritable({ isTTY: true });
Object.defineProperty(process, 'stderr', {
value: stderrStream,
configurable: true,
});
// Set SSH environment to trigger OSC-52 path
process.env['SSH_CONNECTION'] = '1';
await copyToClipboard('windows-ssh-test');
expect(mockFs.createWriteStream).not.toHaveBeenCalled();
expect(stderrStream.write).toHaveBeenCalled();
expect(mockClipboardyWrite).not.toHaveBeenCalled();
});
it('uses clipboardy on native Windows without SSH/WSL', async () => {
mockProcess.platform = 'win32';
mockClipboardyWrite.mockResolvedValue(undefined);
await copyToClipboard('windows-native-test');
// Fallback to clipboardy and not /dev/tty
expect(mockClipboardyWrite).toHaveBeenCalledWith('windows-native-test');
expect(mockFs.createWriteStream).not.toHaveBeenCalled();
});
}); });
describe('getUrlOpenCommand', () => { describe('getUrlOpenCommand', () => {
+12 -6
View File
@@ -66,13 +66,19 @@ const SCREEN_DCS_CHUNK_SIZE = 240;
type TtyTarget = { stream: Writable; closeAfter: boolean } | null; type TtyTarget = { stream: Writable; closeAfter: boolean } | null;
const pickTty = (): TtyTarget => { const pickTty = (): TtyTarget => {
// Prefer the controlling TTY to avoid interleaving escape sequences with piped stdout. // /dev/tty is only available on Unix-like systems (Linux, macOS, BSD, etc.)
try { if (process.platform !== 'win32') {
const devTty = fs.createWriteStream('/dev/tty'); // Prefer the controlling TTY to avoid interleaving escape sequences with piped stdout.
return { stream: devTty, closeAfter: true }; try {
} catch { const devTty = fs.createWriteStream('/dev/tty');
// fall through // Prevent unhandled 'error' events from crashing the process.
devTty.on('error', () => {});
return { stream: devTty, closeAfter: true };
} catch {
// fall through - /dev/tty not accessible
}
} }
if (process.stderr?.isTTY) if (process.stderr?.isTTY)
return { stream: process.stderr, closeAfter: false }; return { stream: process.stderr, closeAfter: false };
if (process.stdout?.isTTY) if (process.stdout?.isTTY)