From fc46f7c847754cf3336c5a8db2cce3014d0a18e0 Mon Sep 17 00:00:00 2001 From: Valerii Kovalskii Date: Sun, 19 Jul 2026 05:37:11 +0200 Subject: [PATCH] fix: make self-update restart reliably (v7.5.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Update Now" flow installed the new version but then died without coming back up — the port was freed but nothing relistened, so the UI kept showing the stale version and the page failed to reload. Root cause: the restart spawned a child inside a `process.on('exit')` handler. The event loop is already draining at exit time, so the detached child frequently never survived. Fix: spawn a fully-detached, unref'd restarter (own process group, stdio ignored) BEFORE exiting, then exit after a short tick to release the port. Restarter reuses the same port/host and skips reopening the browser. Also raise the npm-install timeout 60s -> 120s. Co-Authored-By: Claude Opus 4.8 (1M context) --- package.json | 2 +- src/changelog.js | 10 ++++++++++ src/server.js | 30 ++++++++++++++++++++---------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index e896869..4030a60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codbash-app", - "version": "7.5.1", + "version": "7.5.2", "description": "Dashboard + CLI for AI coding agents — Claude Code, Codex, Cursor, OpenCode, Kiro. View, search, resume, convert, sync sessions.", "bin": { "codbash": "./bin/cli.js", diff --git a/src/changelog.js b/src/changelog.js index e10f32b..60d941c 100644 --- a/src/changelog.js +++ b/src/changelog.js @@ -1,6 +1,16 @@ 'use strict'; const CHANGELOG = [ + { + version: '7.5.2', + date: '2026-07-19', + title: 'Reliable self-update restart', + changes: [ + 'Fix: "Update Now" no longer leaves the server dead after installing the new version — the restart now spawns a fully-detached process instead of an unreliable process.on("exit") handler', + 'Self-update restarter reclaims the same port/host and skips reopening the browser (the existing tab reloads itself)', + 'npm install timeout during self-update raised from 60s to 120s for slower connections', + ], + }, { version: '7.5.1', date: '2026-05-26', diff --git a/src/server.js b/src/server.js index cba2060..7778dfc 100644 --- a/src/server.js +++ b/src/server.js @@ -795,17 +795,27 @@ function startServer(host, port, openBrowser = true) { json(res, { ok: true, message: 'Updating... Page will reload.' }); // Run update in background after response is sent setTimeout(() => { - const { execSync } = require('child_process'); + const { execSync, spawn } = require('child_process'); try { - execSync('npm i -g codbash-app@latest', { stdio: 'inherit', timeout: 60000 }); - log('UPDATE', 'Updated. Restarting...'); - // Restart the process - process.on('exit', () => { - require('child_process').spawn(process.argv[0], process.argv.slice(1), { - detached: true, stdio: 'inherit' - }).unref(); - }); - process.exit(0); + execSync('npm i -g codbash-app@latest', { stdio: 'inherit', timeout: 120000 }); + log('UPDATE', 'Update installed. Restarting server...'); + // Spawn a FULLY-detached restarter BEFORE exiting. Spawning inside a + // `process.on('exit')` handler is unreliable — the event loop is already + // draining, so the child frequently never survives and the server ends + // up dead (nothing reclaims the port). Instead: detach + unref + ignore + // stdio so the child outlives us, become its own process-group leader + // (so the restarter's `lsof … | kill -9` on the port can't take it down), + // then exit cleanly. The restarter loads the freshly-installed code from + // the same argv[1] path that npm just overwrote. + const child = spawn( + process.argv[0], + [process.argv[1], 'restart', `--port=${port}`, `--host=${host}`, '--no-browser'], + { detached: true, stdio: 'ignore' } + ); + child.unref(); + // Give the OS a tick to fully spawn the detached child before we exit + // and release the port for the restarter to grab. + setTimeout(() => process.exit(0), 250); } catch (e) { log('ERROR', `Update failed: ${e.message}`); }