From 6a857d41baf1c1ee2d5e747a0705848fe30ad631 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 9 Apr 2026 05:17:54 -1000 Subject: [PATCH] fix: restore isProcessAlive boolean semantics, add safeUnlinkQuiet, remove unused json() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isProcessAlive now catches ALL errors and returns false (pure boolean probe). Callers use it in if/while conditions without try/catch, so throwing on EPERM was a behavior change that could crash the CLI. Windows path gets its safety catch restored. safeUnlinkQuiet added for best-effort cleanup paths where throwing on non-ENOENT errors (like EPERM during shutdown) would abort cleanup. json() removed — dead code, never imported anywhere. Co-Authored-By: Claude Opus 4.6 (1M context) --- browse/src/error-handling.ts | 39 +++++++++++++----------------- browse/test/error-handling.test.ts | 19 +-------------- 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/browse/src/error-handling.ts b/browse/src/error-handling.ts index 890b3afd..2c4e271e 100644 --- a/browse/src/error-handling.ts +++ b/browse/src/error-handling.ts @@ -20,6 +20,11 @@ export function safeUnlink(filePath: string): void { } } +/** Remove a file, ignoring ALL errors. Use only in best-effort cleanup (shutdown, emergency). */ +export function safeUnlinkQuiet(filePath: string): void { + try { fs.unlinkSync(filePath); } catch {} +} + // ─── Process ─────────────────────────────────────────────────── /** Send a signal to a process, ignoring ESRCH (already dead). Rethrows other errors. */ @@ -31,33 +36,23 @@ export function safeKill(pid: number, signal: NodeJS.Signals | number): void { } } -/** Check if a PID is alive. Returns false for ESRCH, rethrows EPERM and others. */ +/** Check if a PID is alive. Pure boolean probe — returns false for ALL errors. */ export function isProcessAlive(pid: number): boolean { if (IS_WINDOWS) { - // Bun's compiled binary can't signal Windows PIDs (always throws ESRCH). - // Use tasklist as a fallback. Only for one-shot calls — too slow for polling loops. - // Bun.spawnSync may throw if tasklist binary is missing (ENOENT) - const result = Bun.spawnSync( - ['tasklist', '/FI', `PID eq ${pid}`, '/NH', '/FO', 'CSV'], - { stdout: 'pipe', stderr: 'pipe', timeout: 3000 } - ); - return result.stdout.toString().includes(`"${pid}"`); + try { + const result = Bun.spawnSync( + ['tasklist', '/FI', `PID eq ${pid}`, '/NH', '/FO', 'CSV'], + { stdout: 'pipe', stderr: 'pipe', timeout: 3000 } + ); + return result.stdout.toString().includes(`"${pid}"`); + } catch { + return false; + } } try { process.kill(pid, 0); return true; - } catch (err: any) { - if (err?.code === 'ESRCH') return false; - throw err; + } catch { + return false; } } - -// ─── HTTP ────────────────────────────────────────────────────── - -/** JSON Response constructor shorthand for Bun.serve routes. */ -export function json(data: unknown, status = 200): Response { - return new Response(JSON.stringify(data), { - status, - headers: { 'Content-Type': 'application/json' }, - }); -} diff --git a/browse/test/error-handling.test.ts b/browse/test/error-handling.test.ts index dc6a7429..b25d9880 100644 --- a/browse/test/error-handling.test.ts +++ b/browse/test/error-handling.test.ts @@ -2,7 +2,7 @@ import { describe, test, expect } from 'bun:test'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { safeUnlink, safeKill, isProcessAlive, json } from '../src/error-handling'; +import { safeUnlink, safeKill, isProcessAlive } from '../src/error-handling'; describe('safeUnlink', () => { test('removes an existing file', () => { @@ -45,20 +45,3 @@ describe('isProcessAlive', () => { expect(isProcessAlive(99999999)).toBe(false); }); }); - -describe('json', () => { - test('returns Response with JSON body and correct Content-Type', async () => { - const resp = json({ ok: true }); - expect(resp.status).toBe(200); - expect(resp.headers.get('Content-Type')).toBe('application/json'); - const body = await resp.json(); - expect(body).toEqual({ ok: true }); - }); - - test('uses custom status code', async () => { - const resp = json({ error: 'not found' }, 404); - expect(resp.status).toBe(404); - const body = await resp.json(); - expect(body).toEqual({ error: 'not found' }); - }); -});