From 72bbe39777c44ee77468cd23e41cd658fdbfead1 Mon Sep 17 00:00:00 2001 From: Jason Myers Date: Mon, 20 Jul 2026 18:27:21 -0500 Subject: [PATCH] fix(browse): clean stale SingletonLock in handoff() before relaunch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handoff() hand-rolled its own chromium-profile path and skipped the stale-lock cleanup that launchHeaded() already does. A prior hard-killed/crashed headed session leaves SingletonLock/Socket/Cookie pointing at a dead PID; Chromium's ProcessSingleton then silently hands startup off to the dead process instead of launching cleanly, and the new process exits within seconds — read as a clean disconnect, taking the whole daemon down with it. Now uses resolveChromiumProfile() + cleanSingletonLocks(), matching launchHeaded(). Co-Authored-By: Claude Sonnet 5 --- browse/src/browser-manager.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/browse/src/browser-manager.ts b/browse/src/browser-manager.ts index f9f3317b5..74a975af9 100644 --- a/browse/src/browser-manager.ts +++ b/browse/src/browser-manager.ts @@ -1551,7 +1551,6 @@ export class BrowserManager { let newContext: BrowserContext; try { const fs = require('fs'); - const path = require('path'); const extensionPath = this.findExtensionPath(); const { STEALTH_LAUNCH_ARGS, buildGStackLaunchArgs } = await import('./stealth'); // Same blink-level stealth flags as launch()/launchHeaded(). Without @@ -1568,9 +1567,18 @@ export class BrowserManager { console.log('[browse] Handoff: extension not found — headed mode without side panel'); } - const userDataDir = path.join(process.env.HOME || '/tmp', '.gstack', 'chromium-profile'); + const userDataDir = resolveChromiumProfile(); fs.mkdirSync(userDataDir, { recursive: true }); + // Pre-launch cleanup of stale SingletonLock/Socket/Cookie, same as + // launchHeaded(). Without this, a prior hard-killed/crashed headed + // session leaves lockfiles pointing at a dead PID; Chromium's + // ProcessSingleton then silently hands off startup to the (dead or + // zombie) old process instead of launching cleanly, and the new + // process exits shortly after — which reads as a clean disconnect + // and takes the whole daemon down with it. + cleanSingletonLocks(userDataDir); + // T1: same automation-tell-stripping defaults as launchHeaded(). // The handoff path (headless → headed re-launch) takes the same // anti-detection posture.