From ad38e006f633cb263c99d13a4439b4084ebf0241 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 9 Apr 2026 05:17:54 -1000 Subject: [PATCH] fix: use safeUnlinkQuiet in shutdown and cleanup paths Shutdown, emergency cleanup, and disconnect paths should never throw on file deletion failures. Switched from safeUnlink (throws on EPERM) to safeUnlinkQuiet (swallows all errors) in these best-effort paths. Normal operation paths (startup, lock release) keep safeUnlink. Co-Authored-By: Claude Opus 4.6 (1M context) --- browse/src/cli.ts | 10 +++++----- browse/src/server.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/browse/src/cli.ts b/browse/src/cli.ts index f5129547..ae287515 100644 --- a/browse/src/cli.ts +++ b/browse/src/cli.ts @@ -11,7 +11,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { safeUnlink, safeKill, isProcessAlive } from './error-handling'; +import { safeUnlink, safeUnlinkQuiet, safeKill, isProcessAlive } from './error-handling'; import { resolveConfig, ensureStateDir, readVersionHash } from './config'; const config = resolveConfig(); @@ -812,11 +812,11 @@ Refs: After 'snapshot', use @e1, @e2... as selectors: // Clean up Chromium profile locks (can persist after crashes) for (const lockFile of ['SingletonLock', 'SingletonSocket', 'SingletonCookie']) { - safeUnlink(path.join(profileDir, lockFile)); + safeUnlinkQuiet(path.join(profileDir, lockFile)); } // Delete stale state file - safeUnlink(config.stateFile); + safeUnlinkQuiet(config.stateFile); console.log('Launching headed Chromium with extension + sidebar agent...'); try { @@ -945,9 +945,9 @@ Refs: After 'snapshot', use @e1, @e2... as selectors: // Clean profile locks and state file const profileDir = path.join(process.env.HOME || '/tmp', '.gstack', 'chromium-profile'); for (const lockFile of ['SingletonLock', 'SingletonSocket', 'SingletonCookie']) { - safeUnlink(path.join(profileDir, lockFile)); + safeUnlinkQuiet(path.join(profileDir, lockFile)); } - safeUnlink(config.stateFile); + safeUnlinkQuiet(config.stateFile); console.log('Disconnected (server was unresponsive — force cleaned).'); process.exit(0); } diff --git a/browse/src/server.ts b/browse/src/server.ts index 4f97afb9..c370ecc7 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -38,7 +38,7 @@ import { emitActivity, subscribe, getActivityAfter, getActivityHistory, getSubsc import { inspectElement, modifyStyle, resetModifications, getModificationHistory, detachSession, type InspectorResult } from './cdp-inspector'; // Bun.spawn used instead of child_process.spawn (compiled bun binaries // fail posix_spawn on all executables including /bin/bash) -import { safeUnlink, safeKill } from './error-handling'; +import { safeUnlink, safeUnlinkQuiet, safeKill } from './error-handling'; import * as fs from 'fs'; import * as net from 'net'; import * as path from 'path'; @@ -1188,11 +1188,11 @@ async function shutdown() { // Clean up Chromium profile locks (prevent SingletonLock on next launch) const profileDir = path.join(process.env.HOME || '/tmp', '.gstack', 'chromium-profile'); for (const lockFile of ['SingletonLock', 'SingletonSocket', 'SingletonCookie']) { - safeUnlink(path.join(profileDir, lockFile)); + safeUnlinkQuiet(path.join(profileDir, lockFile)); } // Clean up state file - safeUnlink(config.stateFile); + safeUnlinkQuiet(config.stateFile); process.exit(0); } @@ -1204,7 +1204,7 @@ process.on('SIGINT', shutdown); // Defense-in-depth — primary cleanup is the CLI's stale-state detection via health check. if (process.platform === 'win32') { process.on('exit', () => { - safeUnlink(config.stateFile); + safeUnlinkQuiet(config.stateFile); }); } @@ -1223,9 +1223,9 @@ function emergencyCleanup() { // Clean Chromium profile locks const profileDir = path.join(process.env.HOME || '/tmp', '.gstack', 'chromium-profile'); for (const lockFile of ['SingletonLock', 'SingletonSocket', 'SingletonCookie']) { - safeUnlink(path.join(profileDir, lockFile)); + safeUnlinkQuiet(path.join(profileDir, lockFile)); } - safeUnlink(config.stateFile); + safeUnlinkQuiet(config.stateFile); } process.on('uncaughtException', (err) => { console.error('[browse] FATAL uncaught exception:', err.message);