Description
OpenCode Desktop v1.18.3 still crashes with EPIPE (Error: EPIPE: broken pipe, write) in the main process, despite the fix in PR #25980.
The existing fix wraps log.transports.console.writeFn in a try-catch that catches synchronous EPIPE (thrown directly by console.error()). However, EPIPE can also arrive asynchronously via the process.stderr 'error' event, which is outside the call stack and cannot be caught by try-catch.
Stack trace
Error: EPIPE: broken pipe, write
at Socket.write (node:internal/net:75:18)
at writeOrBuffer (node:internal/streams/writable:570:12)
at write (node:internal/streams/writable:499:10)
at Writable.write (node:internal/streams/writable:508:10)
at console.value (node:internal/console/constructor:313:16)
at console.error (node:internal/console/constructor:444:26)
at transport.writeFn
at log.transports.console.writeFn
at transport
at Logger.processMessage
Root cause
The current initConsoleTransport() in out/main/index.js only guards the synchronous path:
function initConsoleTransport() {
const write2 = log.transports.console.writeFn.bind(log.transports.console);
log.transports.console.writeFn = (options) => {
try {
write2(options);
} catch (err) {
if (!isBrokenPipe(err)) throw err;
log.transports.console.level = false;
}
};
}
But process.stderr (a net.Socket) can emit an asynchronous 'error' event when the write fails. This event is not in the call stack of writeFn, so the try-catch does not intercept it. With no process.on('uncaughtException') handler registered, this becomes an uncaught exception that crashes the main process.
Additionally, electron-log's processInternalErrorFn (createDefaultLogger.js:35-42) calls writeFn directly when a transport fails. If the error is async, this can cause recursive calls.
Reproduction
- Launch OpenCode Desktop v1.18.3 on Windows via the desktop shortcut
- Use the app normally -- the crash typically occurs when the logger writes during agent responses
- Observe the "Uncaught Exception" dialog
The exact timing depends on when the parent process closes its end of the stdout/stderr pipe.
Suggested fix
Add an async EPIPE handler on process.stderr in initConsoleTransport():
function initConsoleTransport() {
const write2 = log.transports.console.writeFn.bind(log.transports.console);
log.transports.console.writeFn = (options) => {
try {
write2(options);
} catch (err) {
if (!isBrokenPipe(err)) throw err;
log.transports.console.level = false;
}
};
// Guard against async EPIPE from process.stderr 'error' event
const onStderrError = (err) => {
if (isBrokenPipe(err)) {
log.transports.console.level = false;
process.stderr.removeListener('error', onStderrError);
}
};
process.stderr.on('error', onStderrError);
}
Alternatively, add a scoped process.on('uncaughtException') at the main process entry point that filters EPIPE errors specifically:
process.on('uncaughtException', (error) => {
if (error && (error.code === 'EPIPE' || (error.message && error.message.includes('EPIPE')))) {
log.transports.console.level = false;
return;
}
console.error('Uncaught Exception:', error);
app.quit();
});
Environment
- OS: Windows 10/11
- OpenCode Desktop: 1.18.3
- Launched via: Windows shortcut (
.lnk)
Description
OpenCode Desktop v1.18.3 still crashes with EPIPE (
Error: EPIPE: broken pipe, write) in the main process, despite the fix in PR #25980.The existing fix wraps
log.transports.console.writeFnin a try-catch that catches synchronous EPIPE (thrown directly byconsole.error()). However, EPIPE can also arrive asynchronously via theprocess.stderr'error'event, which is outside the call stack and cannot be caught by try-catch.Stack trace
Root cause
The current
initConsoleTransport()inout/main/index.jsonly guards the synchronous path:But
process.stderr(anet.Socket) can emit an asynchronous'error'event when the write fails. This event is not in the call stack ofwriteFn, so the try-catch does not intercept it. With noprocess.on('uncaughtException')handler registered, this becomes an uncaught exception that crashes the main process.Additionally,
electron-log'sprocessInternalErrorFn(createDefaultLogger.js:35-42) callswriteFndirectly when a transport fails. If the error is async, this can cause recursive calls.Reproduction
The exact timing depends on when the parent process closes its end of the stdout/stderr pipe.
Suggested fix
Add an async EPIPE handler on
process.stderrininitConsoleTransport():Alternatively, add a scoped
process.on('uncaughtException')at the main process entry point that filters EPIPE errors specifically:Environment
.lnk)