-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Bug: EXC_BREAKPOINT (SIGTRAP) crash on macOS ARM64
Describe the bug
The Goose desktop app crashes with EXC_BREAKPOINT (SIGTRAP) on the main thread (CrBrowserMain) after ~29 minutes of use. The crash is a V8 CHECK assertion failure (brk #0) triggered by Buffer.toString() calls inside Node.js stream 'data' event handlers.
Root Cause
Calling data.toString() inside stream 'data' event handlers on the Electron main thread triggers:
Buffer.toString('utf8')
→ node::StringBytes::Encode()
→ v8::String::NewFromUtf8()
→ V8 NEW_STRING macro → .ToHandleChecked()
→ CHECK fails → FATAL → brk #0 → EXC_BREAKPOINT
When V8 cannot allocate a new string (GC pressure, near-OOM), NewFromUtf8 returns empty MaybeLocal, and .ToHandleChecked() hits a CHECK → crash.
3 vulnerable code paths:
goosed.ts:295-309—data.toString()on goosed stdout/stderr (runs continuously)main.ts:1470-1475—data.toString()onps|grepfor check-ollamamain.ts:1527-1532—data.toString()oncatfor read-file handler
Fix (patch included below)
- goosed.ts: Batch raw Buffer chunks, flush via
setImmediate()— movestoString()out of the libuv I/O callback - main.ts check-ollama: Accumulate
Buffer[], convert on'close'after stream ends - main.ts read-file: Replace
spawn('cat')withfs.readFile()on all platforms
Crash signature
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x0000000112b6cef0
ESR: 0xf2000000 (Breakpoint) brk 0
Thread 0 Crashed: CrBrowserMain
0 Electron Framework ares_dns_rr_get_ttl + 3711128
...
29 node::StringBytes::Encode + 272
30 node::Buffer::RegisterExternalReferences + 25908
Environment
- OS & Arch: macOS 26.3 (25D125), ARM64 (Mac15,13 — MacBook Air M3)
- Interface: UI (Desktop)
- Version: 1.25.0-block.202602182139-cd798
- Electron: 40.4.0 / Node 24.13.0 / V8 14.4.258.24
Patch
ui/desktop/src/goosed.ts — defer toString() via setImmediate
Replace lines 295-309:
// OLD (crashes):
goosedProcess.stdout?.on('data', (data: Buffer) => {
logger.info(`goosed stdout for port ${port} and dir ${workingDir}: ${data.toString()}`);
});
goosedProcess.stderr?.on('data', (data: Buffer) => {
const lines = data.toString().split('\n');
...
});With:
let stdoutPending: Buffer[] = [];
let stderrPending: Buffer[] = [];
let stdoutFlushScheduled = false;
let stderrFlushScheduled = false;
goosedProcess.stdout?.on('data', (data: Buffer) => {
stdoutPending.push(Buffer.isBuffer(data) ? data : Buffer.from(data));
if (!stdoutFlushScheduled) {
stdoutFlushScheduled = true;
setImmediate(() => {
stdoutFlushScheduled = false;
if (stdoutPending.length > 0) {
const text = Buffer.concat(stdoutPending).toString('utf8');
stdoutPending = [];
logger.info(`goosed stdout for port ${port} and dir ${workingDir}: ${text}`);
}
});
}
});
goosedProcess.stderr?.on('data', (data: Buffer) => {
stderrPending.push(Buffer.isBuffer(data) ? data : Buffer.from(data));
if (!stderrFlushScheduled) {
stderrFlushScheduled = true;
setImmediate(() => {
stderrFlushScheduled = false;
if (stderrPending.length > 0) {
const text = Buffer.concat(stderrPending).toString('utf8');
stderrPending = [];
const lines = text.split('\n');
for (const line of lines) {
if (line.trim()) {
errorLog.push(line);
if (isFatalError(line)) {
logger.error(`goosed stderr for port ${port} and dir ${workingDir}: ${line}`);
}
}
}
}
});
}
});ui/desktop/src/main.ts check-ollama — accumulate Buffers
Replace let output = ''; let errorOutput = ''; and the data handlers with:
const outputChunks: Buffer[] = [];
const errorChunks: Buffer[] = [];
ps.stdout.pipe(grep.stdin);
grep.stdout.on('data', (data: Buffer) => {
outputChunks.push(Buffer.isBuffer(data) ? data : Buffer.from(data));
});
grep.stderr.on('data', (data: Buffer) => {
errorChunks.push(Buffer.isBuffer(data) ? data : Buffer.from(data));
});
grep.on('close', (code) => {
const output = Buffer.concat(outputChunks).toString('utf8');
const errorOutput = Buffer.concat(errorChunks).toString('utf8');
// ... rest unchangedui/desktop/src/main.ts read-file — use fs.readFile on all platforms
Replace the entire read-file handler with:
ipcMain.handle('read-file', async (_event, filePath) => {
try {
const expandedPath = expandTilde(filePath);
const buffer = await fs.readFile(expandedPath);
return { file: buffer.toString('utf8'), filePath: expandedPath, error: null, found: true };
} catch (error) {
console.error('Error reading file:', error);
return { file: '', filePath: expandTilde(filePath), error, found: false };
}
});Full git patch is available at: https://github.com/AE-33/goose (branch: fix/electron-stream-crash-exc-breakpoint)