@samitouri / QOSami-HFS / commits / a0628016

fix: infinite loop when quitting caused by closed terminal

Massimo Melina committed May 6, 2026 at 00:58 UTC a0628016bc9e2905e705cb844b2eed7f4fa045c5
1 file changed +23 -2
src/consoleLog.ts
+23 -2
@@ -6,6 +6,14 @@ import { argv } from './argv'
6 export const consoleLog: Array<{ ts: Date, k: string, msg: string }> = []
7 const originalConsoleLog = console.log
8 const f = argv.consoleFile ? createWriteStream(argv.consoleFile, { flags: 'a', encoding: 'utf8' }) : null
9 +let terminalOutputBroken = false
10 +for (const stream of [process.stdout, process.stderr])
11 + stream.on('error', err => {
12 + if (!isBrokenTerminalOutput(err))
13 + throw err
14 + // after the terminal/pipe is gone, further console writes would just re-emit the same process-level error
15 + terminalOutputBroken = true
16 + })
17 for (const k of ['log','warn','error','debug'] as const) {
18 const original = console[k]
19 console[k as 'log'] = (...args: any[]) => {
@@ -23,7 +31,14 @@ for (const k of ['log','warn','error','debug'] as const) {
31 if (k !== 'log')
32 args.unshift('!')
33 }
26 - return original(formatTime(ts), ...args) // bundled nodejs doesn't have locales (and apparently uses en-US)
34 + if (!terminalOutputBroken) {
35 + try { return original(formatTime(ts), ...args) } // bundled nodejs doesn't have locales (and apparently uses en-US)
36 + catch (err) {
37 + if (!isBrokenTerminalOutput(err))
38 + throw err
39 + terminalOutputBroken = true
40 + }
41 + }
42 }
43 Object.assign(console[k], { original })
44 }
@@ -56,6 +71,12 @@ function safeJoin(a: unknown[]): string {
71 }
72 }
73
74 +function isBrokenTerminalOutput(err: unknown) {
75 + const code = (err as NodeJS.ErrnoException)?.code
76 + return code === 'EPIPE' || code === 'EIO'
77 + || code === 'ERR_STREAM_DESTROYED' || code === 'ERR_STREAM_WRITE_AFTER_END'
78 +}
79 +
80 export function consoleHint(msg: string) {
81 console.log("HINT: "+ msg)
61 -}
\ No newline at end of file
82 +}