mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 11:53:10 +08:00
ec5094fb57
Launching from Finder or the Dock leaves the main process with stdio that has no reader. Writing there fails asynchronously with EPIPE from inside the stream machinery, and with no `error` listener Node escalates it to an uncaught exception — which Electron shows as "A JavaScript error occurred in the main process". This is reachable in ordinary use: the sidecar exit handler logs a line every time a sidecar dies, so any crashed or killed sidecar could raise that dialog. Guarding that one call site would not help, since the main process has ~25 console call sites and all of them write to the same two streams. A try/catch at the call site cannot help either, because the throw happens off-stack. Install the guard on stdout/stderr before anything logs. Losing a diagnostic line is acceptable; killing the user's session over one is not — real diagnostics already persist to a file through appendHostDiagnostic.
45 lines
1.7 KiB
TypeScript
45 lines
1.7 KiB
TypeScript
import { EventEmitter } from 'node:events'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { installStdioWriteFailureGuards } from './stdioGuards'
|
|
|
|
const makeStream = () => new EventEmitter()
|
|
|
|
describe('Electron stdio write failure guards', () => {
|
|
it('keeps an EPIPE write failure from escalating to an uncaught exception', () => {
|
|
const stdout = makeStream()
|
|
installStdioWriteFailureGuards([stdout])
|
|
|
|
const epipe: NodeJS.ErrnoException = Object.assign(new Error('write EPIPE'), {
|
|
code: 'EPIPE',
|
|
})
|
|
|
|
// Without a listener, EventEmitter rethrows 'error' — exactly how Node
|
|
// turns a failed console.log into the Electron crash dialog.
|
|
expect(() => stdout.emit('error', epipe)).not.toThrow()
|
|
})
|
|
|
|
it('guards stderr as well as stdout', () => {
|
|
const stdout = makeStream()
|
|
const stderr = makeStream()
|
|
|
|
expect(installStdioWriteFailureGuards([stdout, stderr])).toBe(2)
|
|
expect(() => stderr.emit('error', new Error('write EPIPE'))).not.toThrow()
|
|
})
|
|
|
|
it('does not stack duplicate listeners when installed twice', () => {
|
|
const stdout = makeStream()
|
|
|
|
expect(installStdioWriteFailureGuards([stdout])).toBe(1)
|
|
expect(installStdioWriteFailureGuards([stdout])).toBe(0)
|
|
expect(stdout.listenerCount('error')).toBe(1)
|
|
})
|
|
|
|
it('defaults to the real process stdio streams', () => {
|
|
// Idempotent by design, so calling it here cannot disturb the test runner's
|
|
// own streams beyond the single no-op listener the app installs at startup.
|
|
expect(installStdioWriteFailureGuards()).toBeGreaterThanOrEqual(0)
|
|
expect(process.stdout.listenerCount('error')).toBeGreaterThan(0)
|
|
expect(process.stderr.listenerCount('error')).toBeGreaterThan(0)
|
|
})
|
|
})
|