From 2eb8550a728666e4538419d852631b879ee976ee Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 3 Oct 2026 13:05:20 -0700 Subject: [PATCH 1/2] fix(client): register running sessions missed while disconnected --- packages/client/src/solid/data.ts | 5 +++++ packages/client/test/solid-data.test.ts | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/client/src/solid/data.ts b/packages/client/src/solid/data.ts index 4a1666f88b7b..dae0ca50d137 100644 --- a/packages/client/src/solid/data.ts +++ b/packages/client/src/solid/data.ts @@ -608,6 +608,11 @@ export function createData(config: CreateDataInput) { }) activeUpdates = undefined setStore("session", "active", reconcile(Object.fromEntries(snapshot))) + // Sessions created while disconnected missed `session.created`; without their info they never + // join a family, so a running child would not mark its root as running. + snapshot.forEach((status, id) => { + if (status === "running" && !store.session.info[id]) refresh(() => result.session.sync(id)) + }) }) .catch(() => { if (activeUpdates === updates) activeUpdates = undefined diff --git a/packages/client/test/solid-data.test.ts b/packages/client/test/solid-data.test.ts index 07b8dfaef151..2e53c6b13a7f 100644 --- a/packages/client/test/solid-data.test.ts +++ b/packages/client/test/solid-data.test.ts @@ -1016,6 +1016,21 @@ test.each([ } }) +test("adds running sessions created while disconnected to their family", async () => { + const root = { ...session(0), id: "ses_root" } + const child = { ...session(0), id: "ses_child", parentID: "ses_root" } + const setup = activityFixture(() => Response.json({ data: { ses_child: { type: "running" } } }), [child]) + + try { + setup.data.session.remember(root) + setup.emit({ type: "server.connected", data: {} }) + await wait(() => setup.data.session.family("ses_root").includes("ses_child")) + expect(setup.data.session.status("ses_child")).toBe("running") + } finally { + setup.dispose() + } +}) + test("ignores activity snapshots from an older connection", async () => { const reads: ReturnType>[] = [] const setup = activityFixture(() => { @@ -1087,7 +1102,7 @@ function modelInfo(name: string): ModelInfo { } } -function activityFixture(read: () => Response | Promise) { +function activityFixture(read: () => Response | Promise, sessions: SessionInfo[] = []) { const listeners = new Set[0]>() const api = OpenCode.make({ baseUrl: "http://opencode.local", @@ -1095,6 +1110,8 @@ function activityFixture(read: () => Response | Promise) { const request = input instanceof Request ? input : new Request(input, init) const path = new URL(request.url).pathname if (path === "/api/session/active") return read() + const known = sessions.find((item) => path === `/api/session/${item.id}`) + if (known) return Response.json({ data: known }) if (path === "/api/project") return Response.json([]) if (path === "/api/location") return Response.json({ directory: "/project" }) return Response.json({ location: { directory: "/project" }, data: { branch: "main" } }) From 4b1bd31e4283e3c6fabe7291215bb4394c1c2273 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 3 Oct 2026 20:49:46 -0700 Subject: [PATCH 2/2] fix(client): clarify snapshot hydration comment --- packages/client/src/solid/data.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/solid/data.ts b/packages/client/src/solid/data.ts index dae0ca50d137..12f5c2751ff2 100644 --- a/packages/client/src/solid/data.ts +++ b/packages/client/src/solid/data.ts @@ -608,8 +608,8 @@ export function createData(config: CreateDataInput) { }) activeUpdates = undefined setStore("session", "active", reconcile(Object.fromEntries(snapshot))) - // Sessions created while disconnected missed `session.created`; without their info they never - // join a family, so a running child would not mark its root as running. + // Sessions created before this connection (while disconnected, or before the client started) + // never delivered `session.created`; without their info a running child never joins its root's family. snapshot.forEach((status, id) => { if (status === "running" && !store.session.info[id]) refresh(() => result.session.sync(id)) })