From efc9f5d3218505795702da8b2518b89aac688043 Mon Sep 17 00:00:00 2001 From: BinBandit Date: Tue, 10 Mar 2026 20:50:12 +1100 Subject: [PATCH 1/2] fix: invalidate workspace entry cache after turn completion and revert The @-mention file picker showed stale results after the LLM created or deleted files because the workspace index was cached for 15 seconds on both the server (in-memory TTL) and the client (React Query staleTime). Server: export clearWorkspaceIndexCache() from workspaceEntries and call it in CheckpointReactor after turn completion and checkpoint revert so the next search request rescans the filesystem. Client: invalidate projectQueryKeys alongside providerQueryKeys when thread.turn-diff-completed or thread.reverted domain events arrive so React Query refetches workspace entries on the next @-mention query. Closes #791 --- .../server/src/orchestration/Layers/CheckpointReactor.ts | 9 +++++++++ apps/server/src/workspaceEntries.ts | 4 ++++ apps/web/src/routes/__root.tsx | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.ts index da0e08b931..a90a276d37 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.ts @@ -14,6 +14,7 @@ import { checkpointRefForThreadTurn, resolveThreadWorkspaceCwd, } from "../../checkpointing/Utils.ts"; +import { clearWorkspaceIndexCache } from "../../workspaceEntries.ts"; import { CheckpointStore } from "../../checkpointing/Services/CheckpointStore.ts"; import { ProviderService } from "../../provider/Services/ProviderService.ts"; import { CheckpointReactor, type CheckpointReactorShape } from "../Services/CheckpointReactor.ts"; @@ -218,6 +219,10 @@ const make = Effect.gen(function* () { checkpointRef: targetCheckpointRef, }); + // Invalidate the workspace entry cache so the @-mention file picker + // reflects files created or deleted during this turn. + clearWorkspaceIndexCache(checkpointCwd); + const files = yield* checkpointStore .diffCheckpoints({ cwd: checkpointCwd, @@ -493,6 +498,10 @@ const make = Effect.gen(function* () { return; } + // Invalidate the workspace entry cache so the @-mention file picker + // reflects the reverted filesystem state. + clearWorkspaceIndexCache(sessionRuntime.value.cwd); + const rolledBackTurns = Math.max(0, currentTurnCount - event.payload.turnCount); if (rolledBackTurns > 0) { yield* providerService.rollbackConversation({ diff --git a/apps/server/src/workspaceEntries.ts b/apps/server/src/workspaceEntries.ts index 2c215bb6db..c7834d7149 100644 --- a/apps/server/src/workspaceEntries.ts +++ b/apps/server/src/workspaceEntries.ts @@ -409,6 +409,10 @@ async function getWorkspaceIndex(cwd: string): Promise { return nextPromise; } +export function clearWorkspaceIndexCache(cwd: string): void { + workspaceIndexCache.delete(cwd); +} + export async function searchWorkspaceEntries( input: ProjectSearchEntriesInput, ): Promise { diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index 3d7a815f09..0b378ccda9 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -22,6 +22,7 @@ import { preferredTerminalEditor } from "../terminal-links"; import { terminalRunningSubprocessFromEvent } from "../terminalActivity"; import { onServerConfigUpdated, onServerWelcome } from "../wsNativeApi"; import { providerQueryKeys } from "../lib/providerReactQuery"; +import { projectQueryKeys } from "../lib/projectReactQuery"; import { collectActiveTerminalThreadIds } from "../lib/terminalStateCleanup"; export const Route = createRootRouteWithContext<{ @@ -192,6 +193,9 @@ function EventRouter() { if (needsProviderInvalidation) { needsProviderInvalidation = false; void queryClient.invalidateQueries({ queryKey: providerQueryKeys.all }); + // Invalidate workspace entry queries so the @-mention file picker + // reflects files created, deleted, or restored during this turn. + void queryClient.invalidateQueries({ queryKey: projectQueryKeys.all }); } void syncSnapshot(); }, From d8909dc2995ea412110d231e30d5f6450f2c3cce Mon Sep 17 00:00:00 2001 From: BinBandit Date: Tue, 10 Mar 2026 20:59:59 +1100 Subject: [PATCH 2/2] fix: also clear in-flight scan when invalidating workspace index cache clearWorkspaceIndexCache only deleted from workspaceIndexCache, leaving a stale promise in inFlightWorkspaceIndexBuilds. The next getWorkspaceIndex call would return that stale in-flight promise instead of triggering a fresh scan. --- apps/server/src/workspaceEntries.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/server/src/workspaceEntries.ts b/apps/server/src/workspaceEntries.ts index c7834d7149..dbce5c427d 100644 --- a/apps/server/src/workspaceEntries.ts +++ b/apps/server/src/workspaceEntries.ts @@ -411,6 +411,7 @@ async function getWorkspaceIndex(cwd: string): Promise { export function clearWorkspaceIndexCache(cwd: string): void { workspaceIndexCache.delete(cwd); + inFlightWorkspaceIndexBuilds.delete(cwd); } export async function searchWorkspaceEntries(