From 5f55f9cb65b39716461dd26854dacb91089c2ade Mon Sep 17 00:00:00 2001 From: Michael Grundberg Date: Thu, 17 Sep 2026 14:11:58 +0200 Subject: [PATCH] fix(sessions): never let the reboot-restore plan fail recovery The plan build runs inside the try that decides whether restoreMuxSessions() succeeded, so a throw would be caught there, report restoration as failed, and block the stale cleanup and layout reconciliation that follow. An optional convenience would then break the recovery it exists to help. It is guarded on its own now: the correct way for this to fail is an offer nobody gets. Refs #411 Co-Authored-By: Claude Opus 5 (1M context) --- src/web/server.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/web/server.ts b/src/web/server.ts index 2358b2ff..e94b8cdf 100644 --- a/src/web/server.ts +++ b/src/web/server.ts @@ -3081,7 +3081,18 @@ export class WebServer extends EventEmitter { // Build the reboot-restore offer HERE: `dead` is only known after // reconciliation, and the records it reads are pruned by // `cleanupStaleSessions()` as soon as `finalizeRestoredState()` runs. - this.planRebootRestoreOffer(dead, alive.length); + // + // Guarded on its own, because this runs inside the try that decides whether + // RECOVERY succeeded. A throw here would otherwise be caught below, report + // restoration as failed, and block the stale cleanup and layout + // reconciliation that follow — turning an optional convenience into a + // failure of the thing it is supposed to help. An offer nobody gets is the + // correct way for this to fail. + try { + this.planRebootRestoreOffer(dead, alive.length); + } catch (err) { + console.error('[Server] Building the reboot-restore offer failed; continuing recovery:', err); + } if (alive.length > 0 || discovered.length > 0) { console.log(`[Server] Found ${alive.length + discovered.length} alive mux session(s) from previous run`);