fix(docker): address review findings — PID guard, deleteBinary guard, blocked fallback

- Guard child.pid falsy in bootstrap (PID 0 creates immortal phantom lock)
- Add ETXTBSY/EBUSY guard to deleteBinary() (same vuln as downloadAndInstall)
- Fix error message to suggest container restart (not circular ccs docker update)
- Tighten status.blocked guard to handle missing blocker gracefully
This commit is contained in:
Tam Nhu Tran
2026-03-28 18:12:18 -04:00
parent 5eac9c584a
commit 7d410b26d0
3 changed files with 21 additions and 9 deletions
+12 -3
View File
@@ -50,7 +50,7 @@ export async function downloadAndInstall(
if (verbose)
console.error(`[cliproxy] Binary is running, skipping update: ${existingBinary}`);
throw new Error(
`CLIProxy binary is currently running and cannot be replaced. Stop the running instance first, or use 'ccs docker update' to update in place.`
'CLIProxy binary is currently running and cannot be replaced. Restart the container to apply the update.'
);
}
throw error;
@@ -114,8 +114,17 @@ export function deleteBinary(binPath: string, verbose = false, backend?: CLIProx
const effectiveBackend = backend ?? DEFAULT_BACKEND;
const binaryPath = path.join(binPath, getExecutableName(effectiveBackend));
if (fs.existsSync(binaryPath)) {
fs.unlinkSync(binaryPath);
if (verbose) console.error(`[cliproxy] Deleted: ${binaryPath}`);
try {
fs.unlinkSync(binaryPath);
if (verbose) console.error(`[cliproxy] Deleted: ${binaryPath}`);
} catch (error: unknown) {
const code =
error instanceof Error && 'code' in error ? (error as { code: string }).code : '';
if (code === 'ETXTBSY' || code === 'EBUSY') {
throw new Error('CLIProxy binary is currently running and cannot be deleted.');
}
throw error;
}
}
}
+3 -2
View File
@@ -37,8 +37,9 @@ async function runCliproxy(): Promise<number> {
// Register session lock so dashboard can detect the running proxy
let sessionId: string | undefined;
child.on('spawn', () => {
const version = getInstalledCliproxyVersion() ?? undefined;
sessionId = registerSession(CLIPROXY_DEFAULT_PORT, child.pid ?? 0, version, 'plus');
if (!child.pid) return;
const version = getInstalledCliproxyVersion();
sessionId = registerSession(CLIPROXY_DEFAULT_PORT, child.pid, version, 'plus');
});
child.on('error', reject);
+6 -4
View File
@@ -158,14 +158,16 @@ export async function checkCliproxyPort(): Promise<HealthCheck> {
};
}
if (status.blocked && status.blocker) {
if (status.blocked) {
return {
id: 'cliproxy-port',
name: 'CLIProxy Port',
status: 'warning',
message: `Occupied by ${status.blocker.processName}`,
details: `PID ${status.blocker.pid}`,
fix: `Kill process: kill ${status.blocker.pid}`,
message: status.blocker
? `Occupied by ${status.blocker.processName}`
: 'Port occupied by unknown process',
details: status.blocker ? `PID ${status.blocker.pid}` : undefined,
...(status.blocker && { fix: `Kill process: kill ${status.blocker.pid}` }),
};
}