From e50a4846334707a8c1ba4b2f4a41d2f39d602cb8 Mon Sep 17 00:00:00 2001 From: Isaac Sanchez-Hawkins <266845420+isanchez404@users.noreply.github.com> Date: Tue, 12 May 2026 04:05:37 -0400 Subject: [PATCH] fix(sync): drop orphan session parts (#1183) * fix(sync): drop orphan session parts * fix(sync): guard missing part cache --------- Co-authored-by: Isaac Sanchez --- .../ui/src/sync/__tests__/eviction.test.ts | 20 +++++++++++++++++-- packages/ui/src/sync/session-cache.ts | 11 ++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/sync/__tests__/eviction.test.ts b/packages/ui/src/sync/__tests__/eviction.test.ts index 03a2fad4..22166743 100644 --- a/packages/ui/src/sync/__tests__/eviction.test.ts +++ b/packages/ui/src/sync/__tests__/eviction.test.ts @@ -1,11 +1,11 @@ import { describe, expect, test } from "bun:test" -import type { Message, PermissionRequest, QuestionRequest } from "@opencode-ai/sdk/v2/client" +import type { Message, Part, PermissionRequest, QuestionRequest } from "@opencode-ai/sdk/v2/client" import { canDisposeDirectory, hasPendingBlockingRequests, pickDirectoriesToEvict, } from "../eviction" -import { getProtectedSessionCacheIds, pickSessionCacheEvictions } from "../session-cache" +import { dropSessionCaches, getProtectedSessionCacheIds, pickSessionCacheEvictions } from "../session-cache" import { INITIAL_STATE, type DirState, type State } from "../types" const DAY_MS = 24 * 60 * 60 * 1000 @@ -172,4 +172,20 @@ describe("session cache eviction", () => { expect(seen.has("ses_question")).toBe(true) expect(seen.has("ses_streaming")).toBe(true) }) + + test("drops parts for evicted messages without part session ids", () => { + const store = buildState({ + message: { + ses_old: [{ id: "msg_1", role: "user", time: { created: 1 } } as Message], + }, + part: { + msg_1: [{ id: "prt_1", messageID: "msg_1" } as Part], + }, + }) + + dropSessionCaches(store, ["ses_old"]) + + expect(store.message.ses_old).toBe(undefined) + expect(store.part.msg_1).toBe(undefined) + }) }) diff --git a/packages/ui/src/sync/session-cache.ts b/packages/ui/src/sync/session-cache.ts index 297eb62d..37b89f4b 100644 --- a/packages/ui/src/sync/session-cache.ts +++ b/packages/ui/src/sync/session-cache.ts @@ -56,6 +56,17 @@ export function dropSessionCaches(store: SessionCache, sessionIDs: Iterable() + for (const sessionID of stale) { + for (const message of store.message?.[sessionID] ?? []) { + if (message?.id) staleMessageIDs.add(message.id) + } + } + + for (const messageID of staleMessageIDs) { + if (store.part) delete store.part[messageID] + } + for (const key of Object.keys(store.part ?? {})) { const parts = store.part[key] if (!parts?.some((part) => stale.has((part as { sessionID?: string })?.sessionID ?? "")))