fix: Bridge OOM-Crash — Handler defensiv + Node-Heap 4GB [appimage]
Some checks failed
Build AppImage / build (push) Has been cancelled

Der uncaughtException-Handler aus f69f566 las err.stack dreimal und
serialisierte ihn zweimal per JSON.stringify. err.stack ist ein lazy
Getter der bei jedem Zugriff neu formatiert wird — bei V8-nahe-OOM
hat genau das den Abort ausgelöst (Crash-Dump Frames 20-24:
ErrorStackGetter → FormatStackTrace → FatalProcessOutOfMemory).

- Handler: err.stack einmal lesen, auf 2000 Zeichen kürzen, try/catch
  drumrum. Handler darf nicht selbst crashen.
- Bridge-Start: --max-old-space-size=4096 (Node-Default ~2GB reicht
  bei langen Sessions mit großen Thinking-Blocks + Agent-SDK-History
  nicht). Betrifft Daemon- UND stdio-Modus.
This commit is contained in:
Eddy 2026-04-21 16:19:51 +02:00
parent 94c7b9358d
commit 48c7b2a30c
2 changed files with 34 additions and 7 deletions

View file

@ -1089,16 +1089,39 @@ process.on('SIGINT', () => { clearInterval(keepAlive); cleanupDaemon(); process.
process.on('exit', () => { cleanupDaemon(); });
// Globale Fehler-Handler — Bridge darf nicht still abstürzen
// WICHTIG: err.stack ist ein lazy Getter der bei jedem Zugriff neu formatiert wird
// und bei V8-OOM selbst einen OOM-Abort auslösen kann. Daher: einmal lesen, kürzen,
// try/catch drumrum — der Handler darf nicht selbst crashen.
function safeStack(err) {
try {
const raw = (err && err.stack) ? String(err.stack) : '';
return raw.length > 2000 ? raw.slice(0, 2000) + '\n...[gekürzt]' : raw;
} catch {
return '[stack nicht lesbar]';
}
}
process.on('uncaughtException', (err) => {
process.stderr.write(`❌ Unbehandelter Fehler: ${err.message}\n${err.stack}\n`);
sendEvent('bridge-error', { type: 'uncaughtException', message: err.message, stack: err.stack });
sendMonitorEvent('error', `Bridge Crash: ${err.message}`, { stack: err.stack });
try {
const msg = (err && err.message) ? String(err.message).slice(0, 500) : String(err).slice(0, 500);
const stack = safeStack(err);
process.stderr.write(`❌ Unbehandelter Fehler: ${msg}\n${stack}\n`);
sendEvent('bridge-error', { type: 'uncaughtException', message: msg, stack });
sendMonitorEvent('error', `Bridge Crash: ${msg}`, {});
} catch (inner) {
try { process.stderr.write(`❌ Handler-Fehler: ${inner && inner.message}\n`); } catch {}
}
});
process.on('unhandledRejection', (reason) => {
const msg = reason instanceof Error ? reason.message : String(reason);
process.stderr.write(`❌ Unhandled Promise Rejection: ${msg}\n`);
sendEvent('bridge-error', { type: 'unhandledRejection', message: msg });
sendMonitorEvent('error', `Unhandled Rejection: ${msg}`, {});
try {
const msg = reason instanceof Error
? String(reason.message).slice(0, 500)
: String(reason).slice(0, 500);
process.stderr.write(`❌ Unhandled Promise Rejection: ${msg}\n`);
sendEvent('bridge-error', { type: 'unhandledRejection', message: msg });
sendMonitorEvent('error', `Unhandled Rejection: ${msg}`, {});
} catch (inner) {
try { process.stderr.write(`❌ Handler-Fehler: ${inner && inner.message}\n`); } catch {}
}
});
// Bereit-Signal (im stdio-Modus sofort senden, im Daemon-Modus pro Client bei Connect)

View file

@ -173,7 +173,10 @@ fn start_daemon(script_path: &std::path::Path) -> Result<u32, String> {
println!("🔌 Starte Bridge-Daemon: {:?} --socket {}", script_path, SOCKET_PATH);
// --max-old-space-size=4096: Node-Default (~2GB) reicht nicht bei langen Sessions
// mit großen Thinking-Blocks/Agent-SDK-History (KB #crash-oom-stacktrace).
let child = Command::new("node")
.arg("--max-old-space-size=4096")
.arg(script_path)
.arg("--socket")
.arg(SOCKET_PATH)
@ -328,6 +331,7 @@ fn start_bridge_stdio(app: &AppHandle, script_path: &std::path::Path) -> Result<
println!("📂 Bridge Arbeitsverzeichnis: {:?}", project_dir);
let mut child = Command::new("node")
.arg("--max-old-space-size=4096")
.arg(script_path)
.current_dir(project_dir)
.stdin(Stdio::piped())