From 2f4d609290b8fe6f7731156014782de3c681599a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Feb 2026 16:13:59 +0000 Subject: [PATCH] Fix app rename failing with "port is taken" error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit renameApp() killed the old process with .kill() but didn't wait for it to actually exit before restarting on the same port. The OS still had the port bound, causing the new process to fail with "port is taken". Additionally, the old process's exit handler would fire after the rename and corrupt the app's state—releasing the new process's port, setting state to 'invalid', and nullifying the proc reference. Fix by: - Making renameApp async and awaiting proc.exited before proceeding - Guarding the exit handler to bail out when a newer process has taken over https://claude.ai/code/session_01W9GF8Cy7T6V2rnVcoNd1Nc --- src/server/api/apps.ts | 2 +- src/server/apps.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/server/api/apps.ts b/src/server/api/apps.ts index 6a3bdf8..51308bc 100644 --- a/src/server/api/apps.ts +++ b/src/server/api/apps.ts @@ -229,7 +229,7 @@ router.post('/:app/rename', async c => { if (!newName) return c.json({ ok: false, error: 'New name is required' }, 400) - const result = renameApp(appName, newName) + const result = await renameApp(appName, newName) if (!result.ok) return c.json(result, 400) return c.json({ ok: true, name: newName }) diff --git a/src/server/apps.ts b/src/server/apps.ts index b8096ee..95cb936 100644 --- a/src/server/apps.ts +++ b/src/server/apps.ts @@ -142,7 +142,7 @@ export function registerApp(dir: string) { } } -export function renameApp(oldName: string, newName: string): { ok: boolean, error?: string } { +export async function renameApp(oldName: string, newName: string): Promise<{ ok: boolean, error?: string }> { const app = _apps.get(oldName) if (!app) return { ok: false, error: 'App not found' } @@ -155,15 +155,13 @@ export function renameApp(oldName: string, newName: string): { ok: boolean, erro const oldPath = join(APPS_DIR, oldName) const newPath = join(APPS_DIR, newName) - // Stop the app if running + // Stop the app and wait for process to fully exit so the port is freed const wasRunning = app.state === 'running' if (wasRunning) { + const proc = app.proc clearTimers(app) app.proc?.kill() - app.proc = undefined - if (app.port) releasePort(app.port) - app.port = undefined - app.started = undefined + if (proc) await proc.exited } try { @@ -681,6 +679,10 @@ async function runApp(dir: string, port: number) { // Handle process exit proc.exited.then(code => { + // If the app has moved on (e.g. renamed and restarted), this is a + // stale exit handler — don't touch current app state or ports + if (app.proc && app.proc !== proc) return + // Clear all timers clearTimers(app)