fix: crash handling — save session, kill agent, distinct exit codes

Hardened shutdown/crash behavior:
- Browser disconnect exits with code 2 (distinct from crash code 1)
- emergencyCleanup kills agent subprocess and saves session state
- Clean shutdown saves session before exit (chat history persists)
- Clear user message on browser disconnect: "Run $B connect to reconnect"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-21 19:39:56 -07:00
parent 530a4ef22c
commit cf8416290d
2 changed files with 11 additions and 4 deletions
+4 -3
View File
@@ -242,12 +242,13 @@ export class BrowserManager {
await this.newTab();
}
// Browser disconnect handler
// Browser disconnect handler — exit code 2 distinguishes from crashes (1)
if (this.browser) {
this.browser.on('disconnected', () => {
if (this.intentionalDisconnect) return;
console.error('[browse] Real browser disconnected.');
process.exit(1);
console.error('[browse] Real browser disconnected (user closed or crashed).');
console.error('[browse] Run `$B connect` to reconnect.');
process.exit(2);
});
}
+7 -1
View File
@@ -601,6 +601,7 @@ async function shutdown() {
console.log('[browse] Shutting down...');
killAgent();
messageQueue = [];
saveSession(); // Persist chat history before exit
if (agentHealthInterval) clearInterval(agentHealthInterval);
clearInterval(flushInterval);
clearInterval(idleCheckInterval);
@@ -624,10 +625,15 @@ async function shutdown() {
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
// Emergency cleanup for crashes (OOM, uncaught exceptions)
// Emergency cleanup for crashes (OOM, uncaught exceptions, browser disconnect)
function emergencyCleanup() {
if (isShuttingDown) return;
isShuttingDown = true;
// Kill agent subprocess if running
try { killAgent(); } catch {}
// Save session state so chat history persists across crashes
try { saveSession(); } catch {}
// Clean Chromium profile locks
const profileDir = path.join(process.env.HOME || '/tmp', '.gstack', 'chromium-profile');
for (const lockFile of ['SingletonLock', 'SingletonSocket', 'SingletonCookie']) {
try { fs.unlinkSync(path.join(profileDir, lockFile)); } catch {}