diff --git a/src/main/daemon/daemon-child-exit-observer.test.ts b/src/main/daemon/daemon-child-exit-observer.test.ts index 225cbdd1d7a..af9e4c61c3f 100644 --- a/src/main/daemon/daemon-child-exit-observer.test.ts +++ b/src/main/daemon/daemon-child-exit-observer.test.ts @@ -46,6 +46,33 @@ describe('observeDaemonChildExit', () => { ) }) + it('retains only the configured bytes from an oversized stderr chunk', () => { + const child = createChild() + const onExit = vi.fn() + const observer = observeDaemonChildExit(child, onExit, 8) + + observer.markReady() + child.stderr.write(Buffer.from(`${'x'.repeat(1024 * 1024)}FATAL`)) + child.emit('exit', 134, null) + child.stderr.emit('end') + + expect(onExit).toHaveBeenCalledWith(expect.objectContaining({ stderrTail: 'xxxFATAL' })) + }) + + it('reports once when stderr closes before the child exits', () => { + const child = createChild() + const onExit = vi.fn() + const observer = observeDaemonChildExit(child, onExit) + + observer.markReady() + child.stderr.emit('close') + child.emit('exit', 134, null) + child.emit('exit', 134, null) + + expect(onExit).toHaveBeenCalledOnce() + expect(onExit).toHaveBeenCalledWith(expect.objectContaining({ stderrTail: '' })) + }) + it('swallows stderr errors after process exit while awaiting drain', () => { const child = createChild() const onExit = vi.fn() diff --git a/src/main/daemon/daemon-child-exit-observer.ts b/src/main/daemon/daemon-child-exit-observer.ts index 60d891b72fc..593e854815a 100644 --- a/src/main/daemon/daemon-child-exit-observer.ts +++ b/src/main/daemon/daemon-child-exit-observer.ts @@ -28,6 +28,17 @@ export type DaemonChildExitObserver = { stop(options?: { destroyStderr?: boolean }): void } +function appendStderrTail(current: Buffer, chunk: Buffer, maxBytes: number): Buffer { + if (maxBytes <= 0) { + return Buffer.alloc(0) + } + if (chunk.length >= maxBytes) { + return Buffer.from(chunk.subarray(-maxBytes)) + } + const currentBytes = Math.min(current.length, maxBytes - chunk.length) + return Buffer.concat([current.subarray(-currentBytes), chunk], currentBytes + chunk.length) +} + export function observeDaemonChildExit( child: ObservableDaemonChild, recordExit: (observation: DaemonChildExitObservation) => void, @@ -42,7 +53,7 @@ export function observeDaemonChildExit( const onStderr = (chunk: Buffer | string): void => { const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk) - stderrTail = Buffer.concat([stderrTail, bytes]).subarray(-maxStderrBytes) + stderrTail = appendStderrTail(stderrTail, bytes, maxStderrBytes) } const onStderrError = (): void => {} const finalizeExit = (): void => {