Fix app rename failing with "port is taken" error

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
This commit is contained in:
Claude 2026-02-12 16:13:59 +00:00
parent 9c128eaddc
commit 2f4d609290
No known key found for this signature in database
2 changed files with 9 additions and 7 deletions

View File

@ -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 })

View File

@ -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)