{"record":{"id":"be9644fa34d9829b","repo":"bytedance/deer-flow","slug":"failed-to-load-thread-history","errorCode":null,"errorMessage":"Failed to load thread history.","messagePattern":"Failed to load thread history\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/core/threads/hooks.ts","lineNumber":2643,"sourceCode":"    queryKey: threadHistoryQueryKey(threadId),\n    enabled: enabled && Boolean(threadId),\n    initialPageParam: null,\n    queryFn: async ({ pageParam, signal }) => {\n      const url = buildThreadMessagesPageUrl(\n        getBackendBaseURL(),\n        threadId,\n        pageParam ?? undefined,\n      );\n      const response = await fetch(url, {\n        method: \"GET\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        credentials: \"include\",\n        signal,\n      });\n      if (!response.ok) {\n        throw new Error(\n          await readResponseErrorMessage(\n            response,\n            \"Failed to load thread history.\",\n          ),\n        );\n      }\n      return parseThreadMessagesPageResponse(await response.json());\n    },\n    getNextPageParam: getThreadHistoryNextPageParam,\n  });\n\n  const currentMessageRows = useMemo(\n    () => flattenThreadHistoryPages(historyQuery.data?.pages ?? []),\n    [historyQuery.data?.pages],\n  );\n  const [retainedHistory, setRetainedHistory] = useState<{\n    threadId: string;\n    rows: RunMessage[];","sourceCodeStart":2625,"sourceCodeEnd":2661,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/frontend/src/core/threads/hooks.ts#L2625-L2661","documentation":"Default message for a non-ok response when loading thread history pages (GET /api/threads/{id}/messages?before=...) via React Query useInfiniteQuery. readResponseErrorMessage tries the body's detail first; this literal is the fallback. Subsequent parsing failures are handled separately by parseThreadMessagesPageResponse.","triggerScenarios":"History fetch returns 404 (thread pruned/never existed), 401/403 (expired session), 429 (rate limited), 500 (gateway serializer crash), or any error body without a 'detail' field so the fallback text is used.","commonSituations":"Opening a thread link after the thread was deleted, gateway restart during pagination, cookie expiry in a background tab refreshing queries, or a proxy timeout on very long histories.","solutions":["Read the HTTP status of the failing /messages request in the Network tab — it distinguishes auth, missing-thread, and server errors.","404: navigate back to the thread list; the thread no longer exists server-side.","401/403: log in again and let React Query refetch.","5xx: check gateway logs; if it recurs on one thread, that thread's data may be corrupt — try the support-bundle/doctor tooling."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before navigating, optionally HEAD the thread: await fetch(`/api/threads/${id}`, { method: 'HEAD' }) — 404 means don't attempt history load.","typeGuard":null,"tryCatchPattern":"try { const page = await fetchThreadHistory(threadId, param, signal); } catch (e) { if (isAuthError(e)) await relogin(); else if (isNotFound(e)) router.replace('/'); else queryClient.invalidateQueries(historyKey); throw e; }","preventionTips":["Give React Query sensible retry: 2 with exponentialBackoff for 5xx, no retry for 4xx.","Handle 404 history as 'thread gone' UX, not an error toast.","Reuse the same error-detail parser everywhere so fallback text is rare and status codes are preserved."],"tags":["api","threads","history","network","react-query","frontend"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}