{"record":{"id":"685812c5981d102c","repo":"bytedance/deer-flow","slug":"failed-to-delete-local-thread-data","errorCode":null,"errorMessage":"Failed to delete local thread data.","messagePattern":"Failed to delete local thread data\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/core/threads/hooks.ts","lineNumber":3079,"sourceCode":"\nasync function deleteLocalThreadData(threadId: string) {\n  const response = await fetch(\n    `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}`,\n    {\n      method: \"DELETE\",\n    },\n  );\n\n  // A 404 means the thread is already gone — the desired end state. The prior\n  // `apiClient.threads.delete` call hits the same gateway handler (nginx\n  // rewrites /api/langgraph/threads/* to /api/threads/*) and removes the\n  // thread_meta row, so this second delete's ownership guard 404s. Treat it as\n  // success to keep the delete idempotent.\n  if (!response.ok && response.status !== 404) {\n    const error = await response\n      .json()\n      .catch(() => ({ detail: \"Failed to delete local thread data.\" }));\n    throw new Error(error.detail ?? \"Failed to delete local thread data.\");\n  }\n}\n\nasync function deleteThreadEverywhere(\n  apiClient: ThreadDeleteClient,\n  threadId: string,\n) {\n  await apiClient.threads.delete(threadId);\n  await deleteLocalThreadData(threadId);\n}\n\nexport async function findSidecarThreadIdsForParent(\n  apiClient: ThreadSidecarSearchClient,\n  parentThreadId: string,\n) {\n  const threadIds: string[] = [];\n  const limit = 100;\n  let offset = 0;","sourceCodeStart":3061,"sourceCodeEnd":3097,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/frontend/src/core/threads/hooks.ts#L3061-L3097","documentation":"deleteLocalThreadData deletes client-side/local thread artifacts after the primary threads.delete call. A 404 is deliberately treated as success (idempotent delete — see the comment: the first delete already removed thread_meta so the ownership guard 404s). Any other non-ok status parses the body's detail, falling back to this literal when parsing fails.","triggerScenarios":"The secondary delete endpoint returns 500 (storage failure), 401/403 (session expired between the two calls), or a non-JSON error body (proxy error page) making response.json() throw and the fallback text used.","commonSituations":"Deleting a thread while the gateway is restarting; race where the user's session expires mid-delete; a sandboxed/local storage backend unavailable at delete time.","solutions":["Check which status the local-thread-data DELETE returned (only 404 is whitelisted).","401/403: re-auth and retry the delete from the UI.","5xx: inspect gateway logs for the local data delete handler; verify the thread storage backend is reachable.","If the endpoint is legitimately optional in your deployment, treat additional statuses explicitly rather than widening the 404 special case blindly."],"exampleFix":"// before: only 404 tolerated\nif (!response.ok && response.status !== 404) { throw new Error(...); }\n\n// after: also tolerate auth-redirect HTML bodies for clearer errors\nif (!response.ok && response.status !== 404) {\n  const body = await response.json().catch(() => null);\n  throw new Error(body?.detail ?? `Failed to delete local thread data (HTTP ${response.status}).`);\n}","handlingStrategy":"fallback","validationCode":"// Before deleteThreadEverywhere, optionally check thread existence; main guard is server-side idempotency which already whitelists 404.","typeGuard":null,"tryCatchPattern":"try { await deleteThreadEverywhere(apiClient, threadId); } catch (e) { if (/local thread data/i.test(e.message)) { removeThreadFromLocalCache(threadId); /* desired end state reached client-side */ } else throw e; }","preventionTips":["Keep the 404-as-success rule in sync with the gateway handler's ownership guard.","Parse JSON defensively and include status in thrown errors.","Make client cache cleanup independent of server delete success where UX allows."],"tags":["threads","delete","api","idempotency","frontend"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}