From e8b7ac730f108a2ef9393767e070c7703c16e557 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Sat, 28 Mar 2026 19:00:54 -0400 Subject: [PATCH] fix(docker): wrap session registration in try-catch and narrow ETXTBSY guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session registration in spawn handler can throw on lock contention or disk errors — wrap in try-catch to prevent silent proxy-untracked state. Narrow EBUSY catch to ETXTBSY only since EBUSY on non-Linux platforms can mean mount point or directory in use, not running binary. Fix misleading "skip" comment to say "abort". --- src/cliproxy/binary/installer.ts | 13 ++++++++----- src/docker/docker-bootstrap.ts | 10 ++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/cliproxy/binary/installer.ts b/src/cliproxy/binary/installer.ts index 7c8a91b9..2ce8c8d9 100644 --- a/src/cliproxy/binary/installer.ts +++ b/src/cliproxy/binary/installer.ts @@ -36,8 +36,8 @@ export async function downloadAndInstall( fs.mkdirSync(config.binPath, { recursive: true }); // Delete existing binary before install to prevent mismatched binaries. - // Skip if binary is currently running (ETXTBSY) — happens in Docker when - // the dashboard tries to update while bootstrap's instance is active. + // Abort if binary is currently running (ETXTBSY) — cannot replace in-use binary. + // Happens in Docker when dashboard tries to update while bootstrap's instance is active. const existingBinary = path.join(config.binPath, getExecutableName(backend)); if (fs.existsSync(existingBinary)) { try { @@ -46,9 +46,12 @@ export async function downloadAndInstall( } catch (error: unknown) { const code = error instanceof Error && 'code' in error ? (error as { code: string }).code : ''; - if (code === 'ETXTBSY' || code === 'EBUSY') { + // ETXTBSY: Linux-specific error when unlinking a running executable. + // EBUSY on Windows may mean something different (mount point, etc.), + // so only treat ETXTBSY as "binary in use" to avoid misleading messages. + if (code === 'ETXTBSY') { if (verbose) - console.error(`[cliproxy] Binary is running, skipping update: ${existingBinary}`); + console.error(`[cliproxy] Binary is running, cannot replace: ${existingBinary}`); throw new Error( 'CLIProxy binary is currently running and cannot be replaced. Restart the container to apply the update.' ); @@ -120,7 +123,7 @@ export function deleteBinary(binPath: string, verbose = false, backend?: CLIProx } catch (error: unknown) { const code = error instanceof Error && 'code' in error ? (error as { code: string }).code : ''; - if (code === 'ETXTBSY' || code === 'EBUSY') { + if (code === 'ETXTBSY') { throw new Error('CLIProxy binary is currently running and cannot be deleted.'); } throw error; diff --git a/src/docker/docker-bootstrap.ts b/src/docker/docker-bootstrap.ts index 00ea40a2..52c0bb80 100644 --- a/src/docker/docker-bootstrap.ts +++ b/src/docker/docker-bootstrap.ts @@ -38,8 +38,14 @@ async function runCliproxy(): Promise { let sessionId: string | undefined; child.on('spawn', () => { if (!child.pid) return; - const version = getInstalledCliproxyVersion(); - sessionId = registerSession(CLIPROXY_DEFAULT_PORT, child.pid, version, 'plus'); + try { + const version = getInstalledCliproxyVersion(); + sessionId = registerSession(CLIPROXY_DEFAULT_PORT, child.pid, version, 'plus'); + } catch (err) { + console.error( + `[cliproxy] Failed to register session lock: ${err instanceof Error ? err.message : String(err)}` + ); + } }); child.on('error', reject);