Skip to content

Commit dde2fc9

Browse files
committed
fix: prevent infinite recovery loop on empty replay and optimize threadPlanCatalog selector
- Detect no-progress in recoverFromSequenceGap when latestSequence doesn't advance after an empty replay response; fall back to snapshot recovery instead of looping indefinitely. - Replace useShallow+map selector with a direct threads subscription and useMemo so the O(n) mapping only runs when the threads array reference changes, not on every unrelated store update.
1 parent 9084cf5 commit dde2fc9

File tree

2 files changed

+16
-6
lines changed

2 files changed

+16
-6
lines changed

apps/web/src/components/ChatView.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr
2828
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2929
import { useDebouncedValue } from "@tanstack/react-pacer";
3030
import { useNavigate, useSearch } from "@tanstack/react-router";
31-
import { useShallow } from "zustand/react/shallow";
3231
import { gitBranchesQueryOptions, gitCreateWorktreeMutationOptions } from "~/lib/gitReactQuery";
3332
import { projectSearchEntriesQueryOptions } from "~/lib/projectReactQuery";
3433
import { serverConfigQueryOptions, serverQueryKeys } from "~/lib/serverReactQuery";
@@ -591,9 +590,8 @@ export default function ChatView({ threadId }: ChatViewProps) {
591590
);
592591

593592
const fallbackDraftProject = useProjectById(draftThread?.projectId);
594-
const threadPlanCatalog = useStore(
595-
useShallow((store) => store.threads.map(toThreadPlanCatalogEntry)),
596-
);
593+
const threads = useStore((store) => store.threads);
594+
const threadPlanCatalog = useMemo(() => threads.map(toThreadPlanCatalogEntry), [threads]);
597595
const localDraftError = serverThread ? null : (localDraftErrorsByThreadId[threadId] ?? null);
598596
const localDraftThread = useMemo(
599597
() =>

apps/web/src/routes/__root.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,10 @@ function EventRouter() {
250250
return;
251251
}
252252

253+
const sequenceBefore = recovery.getState().latestSequence;
254+
253255
try {
254-
const events = await api.orchestration.replayEvents(recovery.getState().latestSequence);
256+
const events = await api.orchestration.replayEvents(sequenceBefore);
255257
if (!disposed) {
256258
applyEventBatch(events);
257259
}
@@ -261,7 +263,17 @@ function EventRouter() {
261263
return;
262264
}
263265

264-
if (!disposed && recovery.completeReplayRecovery()) {
266+
if (disposed) {
267+
return;
268+
}
269+
270+
if (recovery.getState().latestSequence === sequenceBefore) {
271+
recovery.failReplayRecovery();
272+
void fallbackToSnapshotRecovery();
273+
return;
274+
}
275+
276+
if (recovery.completeReplayRecovery()) {
265277
void recoverFromSequenceGap();
266278
}
267279
};

0 commit comments

Comments
 (0)