From 68b5b46385c52e76079d0ac4bbd42c1638ef3526 Mon Sep 17 00:00:00 2001 From: Vivek Thuravupala <2700229+godfrzero@users.noreply.github.com> Date: Mon, 25 May 2026 15:22:09 -0700 Subject: [PATCH 1/3] fix: app crash if no writable stdout is available #9951 --- packages/insomnia/src/entry.main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/insomnia/src/entry.main.ts b/packages/insomnia/src/entry.main.ts index c1c5e58d5784..85a925b48d2d 100644 --- a/packages/insomnia/src/entry.main.ts +++ b/packages/insomnia/src/entry.main.ts @@ -50,9 +50,9 @@ const dataPath = path.join(app.getPath('userData'), '../', isDevelopment() ? 'insomnia-app' : userDataFolder); app.setPath('userData', dataPath); -initElectronStorage(dataPath); initializeLogging(); +initElectronStorage(dataPath); initializeSentry(); From 1f9de8c16e5e2f5a526daa64e0eb0323c04d6fd5 Mon Sep 17 00:00:00 2001 From: Vivek Thuravupala <2700229+godfrzero@users.noreply.github.com> Date: Mon, 25 May 2026 18:07:07 -0700 Subject: [PATCH 2/3] fix: add handler for async EPIPE error as well --- packages/insomnia/src/main/log.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/insomnia/src/main/log.ts b/packages/insomnia/src/main/log.ts index 1f6e627ce7e6..8dbab67c3703 100644 --- a/packages/insomnia/src/main/log.ts +++ b/packages/insomnia/src/main/log.ts @@ -8,6 +8,17 @@ import { isDevelopment } from '../common/constants'; log.initialize(); export const initializeLogging = () => { + // EPIPE is emitted asynchronously on process.stdout when the read end of the + // pipe is closed (e.g. launched from a desktop entry, or `app | head -0`). + // It surfaces as an 'error' event after the write is dispatched, so it + // escapes any try/catch around the console.log call itself. Without a + // handler it becomes an uncaught exception that crashes the main process. + process.stdout.on('error', (err: NodeJS.ErrnoException) => { + if (err.code !== 'EPIPE') { + throw err; + } + }); + if (isDevelopment()) { // Disable file logging during development log.transports.file.level = false; From 79d4a63011c6f95c65d08fcd9291244a79dae180 Mon Sep 17 00:00:00 2001 From: James Gatz Date: Fri, 29 May 2026 14:42:52 +0200 Subject: [PATCH 3/3] test: add regression tests for EPIPE error handling on Linux --- .../tests/smoke/stdout-epipe.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 packages/insomnia-smoke-test/tests/smoke/stdout-epipe.test.ts diff --git a/packages/insomnia-smoke-test/tests/smoke/stdout-epipe.test.ts b/packages/insomnia-smoke-test/tests/smoke/stdout-epipe.test.ts new file mode 100644 index 000000000000..fc4c11f42d96 --- /dev/null +++ b/packages/insomnia-smoke-test/tests/smoke/stdout-epipe.test.ts @@ -0,0 +1,37 @@ +import { expect } from '@playwright/test'; + +import { test } from '../../playwright/test'; + +// Regression tests for https://github.com/Kong/insomnia/issues/9951 +// On Linux, launching from a .desktop entry (or any context without a writable stdout) +// could crash the main process via an async EPIPE error event on process.stdout. +test.describe('stdout EPIPE resilience', () => { + test('EPIPE error handler is registered on process.stdout at startup', async ({ app }) => { + const listenerCount = await app.evaluate(() => process.stdout.listenerCount('error')); + expect.soft(listenerCount).toBeGreaterThan(0); + }); + + test('app survives an async EPIPE error on stdout', async ({ app }) => { + // Schedule the emit via nextTick so it fires outside evaluate()'s synchronous + // context — mirroring the real scenario where EPIPE arrives as an async I/O + // event from the OS. Without the fix, the throw escapes into the event loop + // as an uncaught exception and crashes the main process. + await app.evaluate(() => { + process.nextTick(() => { + const err: NodeJS.ErrnoException = new Error('write EPIPE'); + err.code = 'EPIPE'; + process.stdout.emit('error', err); + }); + }); + + // Flush the main-process event loop before asserting — setImmediate fires + // after all I/O events in the current iteration, confirming the error event + // was fully handled without triggering an uncaught exception. + await app.evaluate(() => new Promise(resolve => setImmediate(resolve))); + + // The app must still be alive. If the main process crashed from an uncaught + // EPIPE exception, evaluate() would throw with a "Target closed" error. + const alive = await app.evaluate(() => true); + expect.soft(alive).toBe(true); + }); +});