From e5ab6dbcbdb4e3709b0c47610e2f8c863c15a3a6 Mon Sep 17 00:00:00 2001 From: Fizza-Mukhtar Date: Thu, 26 Mar 2026 00:43:18 -0700 Subject: [PATCH] fix(sessions): preserve existing sessions when server returns empty list on reconnect --- apps/app/src/app/context/session.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/app/src/app/context/session.ts b/apps/app/src/app/context/session.ts index c3cf7bbb9..796131c32 100644 --- a/apps/app/src/app/context/session.ts +++ b/apps/app/src/app/context/session.ts @@ -828,6 +828,30 @@ export function createSessionStore(options: { })), }); sessionDebug("sessions:load:filtered", { root: root || null, count: filtered.length }); + + // Guard: do not wipe existing sessions with an empty list when a specific + // scope root was requested. This happens when the engine is restarting + // (app update, worker switch, workspace change) and session.list() returns + // [] before the server has finished indexing — reconcile([]) would destroy + // all session history from the store. We only skip the store update; the + // loadedScopeRoot is still updated so the next successful load can proceed + // normally. If no scope root was given (global load), we always reconcile + // since an empty global list is authoritative. + if (root && filtered.length === 0 && store.sessions.length > 0) { + const existingRootSessions = store.sessions.filter( + (session) => normalizeDirectoryPath(session.directory) === root, + ); + if (existingRootSessions.length > 0) { + sessionDebug("sessions:load:skip-empty-reconcile", { + root, + existingCount: existingRootSessions.length, + reason: "server returned empty list for scoped root — preserving existing sessions", + }); + setLoadedScopeRoot(root); + return; + } + } + setLoadedScopeRoot(root); rememberSessions(filtered); setStore("sessions", reconcile(sortSessionsByActivity(filtered), { key: "id" }));