fix(dev): swallow benign EIO/EPIPE pipe writes on dev restart - #128
Conversation
|
This doesn't happen on every restart, but when restarting the dev app, I get these stacktraces that prevent the app from closing (I use Force Quit to close it). If I believe Claude, this is a dev-only thing. |
|
@big-guy |
I suspect that's what Claude was doing. It would make a change that required a restart and when stopping, I'd see these errors and have two copies running (one with this error and one with the new changes) |
electron-vite closes the Electron child's stdout/stderr pipe on stop/restart. An in-flight console write then fails — synchronously out of console.log on macOS with EIO (libuv returns EIO, not EPIPE), which surfaced as an uncaught exception that crashed the main process on every dev teardown. Install both a synchronous-throw guard (uncaughtException) and async stream 'error' handlers at the top of main, before anything can log. The stream handler alone can't catch the sync throw; a try/catch in debug.ts alone wouldn't cover the many other console.* call sites in main. Strictly scoped to EIO/EPIPE on the std streams so real crashes still propagate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2e6c3e4 to
eb091e4
Compare
The bug
On dev stop/restart (
npm run dev), electron-vite tears down the Electron child process and closes the read end of its stdout/stderr pipe. An in-flightconsole.logthen writes to a broken pipe; on macOS libuv returns EIO (rather than EPIPE). The error surfaces synchronously out ofconsole.log→Writable.write→afterWriteDispatched, and with nothing catching it, it became an uncaught exception that crashed the main process on every dev teardown:The crashing frame is
src/main/debug.ts'slog()(console.log(line)). TheappendFileSyncbelow it was already try/caught, but theconsole.logwas unguarded — and so are the many otherconsole.*calls across main, so a fix scoped only todebug.tswould be insufficient.The fix
Install both a synchronous-throw guard (
uncaughtException) and async stream'error'handlers at the very top ofsrc/main/index.ts, before anything can log. A stream-error handler alone can't catch the synchronous throw; a try/catch indebug.tsalone wouldn't cover the other call sites. Strictly scoped toEIO/EPIPEon the std streams so real crashes still propagate.Purely additive — there were no pre-existing
uncaughtException/ stdout-error handlers insrc/.Verification
npm run typecheck— passednpx electron-vite build— cleannpx vitest run— 115 files / 1333 tests passednpm run dev, booted, killed to trigger teardown — no EIO / uncaught exception in the log🤖 Generated with Claude Code