From 77e3cc4d29961808e0f904d61f5be5cc498c844c Mon Sep 17 00:00:00 2001 From: dannyward630 Date: Thu, 18 Jun 2026 21:25:47 +0200 Subject: [PATCH] fix(app): ignore stale warm session references --- .../src/context/global-sync/bootstrap.test.ts | 58 +++++++++++++++++-- .../app/src/context/global-sync/bootstrap.ts | 21 +++++-- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/packages/app/src/context/global-sync/bootstrap.test.ts b/packages/app/src/context/global-sync/bootstrap.test.ts index 8393b0474258..79cd663d3dc1 100644 --- a/packages/app/src/context/global-sync/bootstrap.test.ts +++ b/packages/app/src/context/global-sync/bootstrap.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test" import { createStore } from "solid-js/store" import { QueryClient } from "@tanstack/solid-query" -import type { Config, OpencodeClient, Project } from "@opencode-ai/sdk/v2/client" +import type { Config, OpencodeClient, PermissionRequest, Project } from "@opencode-ai/sdk/v2/client" import type { NormalizedProviderListResponse } from "@opencode-ai/ui/context" import { bootstrapDirectory, loadPathQuery, loadProvidersQuery } from "./bootstrap" import type { State, VcsCache } from "./types" @@ -10,9 +10,8 @@ import { ServerScope } from "@/utils/server-scope" const provider = { all: new Map(), connected: [], default: {} } satisfies NormalizedProviderListResponse describe("bootstrapDirectory", () => { - test("marks a loading directory partial during bootstrap and complete after success", async () => { - const mcpReads: string[] = [] - const [store, setStore] = createStore({ + function createState(overrides: Partial = {}) { + return createStore({ status: "loading", agent: [], command: [], @@ -42,7 +41,13 @@ describe("bootstrapDirectory", () => { message: {}, part: {}, part_text_accum_delta: {}, + ...overrides, }) + } + + test("marks a loading directory partial during bootstrap and complete after success", async () => { + const mcpReads: string[] = [] + const [store, setStore] = createState() await bootstrapDirectory({ directory: "/project", @@ -90,6 +95,51 @@ describe("bootstrapDirectory", () => { expect(store.status).toBe("complete") expect(mcpReads).toEqual([]) }) + + test("ignores stale session references while warming permission sessions", async () => { + const missing = new Error("Session not found: ses_missing", { cause: { status: 404 } }) + const permission = { id: "perm_1", sessionID: "ses_missing" } as PermissionRequest + const [store, setStore] = createState() + + await bootstrapDirectory({ + directory: "/project", + scope: ServerScope.local, + mcp: false, + global: { + config: {} satisfies Config, + path: { state: "", config: "", worktree: "/project", directory: "/project", home: "/home" }, + project: [{ id: "project", worktree: "/project" } as Project], + provider, + }, + sdk: { + app: { agents: async () => ({ data: [] }) }, + config: { get: async () => ({ data: {} }) }, + session: { + get: async () => { + throw missing + }, + status: async () => ({ data: {} }), + }, + vcs: { get: async () => ({ data: undefined }) }, + command: { list: async () => ({ data: [] }) }, + permission: { list: async () => ({ data: [permission] }) }, + question: { list: async () => ({ data: [] }) }, + mcp: { status: async () => ({ data: {} }) }, + provider: { list: async () => ({ data: { all: [], connected: [], default: {} } }) }, + } as unknown as OpencodeClient, + store, + setStore, + vcsCache: { setStore() {} } as unknown as VcsCache, + loadSessions() {}, + translate: (key) => key, + queryClient: new QueryClient(), + }) + + await new Promise((resolve) => setTimeout(resolve, 80)) + + expect(store.status).toBe("complete") + expect(store.permission.ses_missing).toEqual([permission]) + }) }) describe("query keys", () => { diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index bac9c69280d0..f8728f368894 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -160,6 +160,12 @@ function mergeSession(setStore: SetStoreFunction, session: Session) { }) } +const isNotFound = (error: unknown) => + error instanceof Error && + typeof error.cause === "object" && + error.cause !== null && + (error.cause as { status?: unknown }).status === 404 + function warmSessions(input: { ids: string[] store: Store @@ -171,11 +177,16 @@ function warmSessions(input: { if (ids.length === 0) return Promise.resolve() return Promise.all( ids.map((sessionID) => - retry(() => input.sdk.session.get({ sessionID })).then((x) => { - const session = x.data - if (!session?.id) return - mergeSession(input.setStore, session) - }), + retry(() => input.sdk.session.get({ sessionID })) + .then((x) => { + const session = x.data + if (!session?.id) return + mergeSession(input.setStore, session) + }) + .catch((error) => { + if (isNotFound(error)) return + throw error + }), ), ).then(() => undefined) }