fix(docker): wrap session registration in try-catch and narrow ETXTBSY guard

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".
This commit is contained in:
Tam Nhu Tran
2026-03-28 19:00:54 -04:00
parent 7d410b26d0
commit e8b7ac730f
2 changed files with 16 additions and 7 deletions
+8 -5
View File
@@ -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;
+8 -2
View File
@@ -38,8 +38,14 @@ async function runCliproxy(): Promise<number> {
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);