From 546cf522220b38e4767b843d9325db0f0b2e48d2 Mon Sep 17 00:00:00 2001 From: ashim-hq Date: Sat, 18 Apr 2026 11:39:20 +0800 Subject: [PATCH] fix: install lock used server PID causing permanent stale lock The lock file stored process.pid (Node server PID) which is always alive. When releaseInstallLock wasn't called (e.g., error path), the lock persisted forever, making the bundle stuck in "installing" and blocking all future installs. Fix: remove PID from lock entirely. Use simple file existence as mutex. On server startup, unconditionally delete any lock file. --- apps/api/src/lib/feature-status.ts | 43 +++++------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/apps/api/src/lib/feature-status.ts b/apps/api/src/lib/feature-status.ts index 2f0163a7..5103b789 100644 --- a/apps/api/src/lib/feature-status.ts +++ b/apps/api/src/lib/feature-status.ts @@ -129,38 +129,16 @@ export function markUninstalled(bundleId: string): void { interface LockData { bundleId: string; startedAt: string; - pid: number; -} - -function isPidAlive(pid: number): boolean { - try { - process.kill(pid, 0); - return true; - } catch { - return false; - } } export function acquireInstallLock(bundleId: string): boolean { if (existsSync(LOCK_PATH)) { - try { - const raw = readFileSync(LOCK_PATH, "utf-8"); - const lock = JSON.parse(raw) as LockData; - if (isPidAlive(lock.pid)) { - return false; - } - // PID is dead — stale lock, remove it - unlinkSync(LOCK_PATH); - } catch { - // Corrupt lock file, remove it - unlinkSync(LOCK_PATH); - } + return false; } const lock: LockData = { bundleId, startedAt: new Date().toISOString(), - pid: process.pid, }; writeFileSync(LOCK_PATH, JSON.stringify(lock, null, 2), "utf-8"); return true; @@ -183,15 +161,8 @@ export function getInstallingBundle(): { try { const raw = readFileSync(LOCK_PATH, "utf-8"); const lock = JSON.parse(raw) as LockData; - - if (!isPidAlive(lock.pid)) { - unlinkSync(LOCK_PATH); - return null; - } - return { bundleId: lock.bundleId, startedAt: lock.startedAt }; } catch { - // Corrupt lock file try { unlinkSync(LOCK_PATH); } catch { @@ -289,17 +260,15 @@ export function recoverInterruptedInstalls(): void { } } - // 4. Check stale lock + // 4. Delete stale lock — if the server is starting up, any previous install is dead if (existsSync(LOCK_PATH)) { try { const raw = readFileSync(LOCK_PATH, "utf-8"); const lock = JSON.parse(raw) as LockData; - if (!isPidAlive(lock.pid)) { - unlinkSync(LOCK_PATH); - console.warn( - `[feature-status] Removed stale install lock for "${lock.bundleId}" (PID ${lock.pid} is dead)`, - ); - } + unlinkSync(LOCK_PATH); + console.warn( + `[feature-status] Removed stale install lock for "${lock.bundleId}" (server restarted)`, + ); } catch { try { unlinkSync(LOCK_PATH);