bytedance/deer-flow · error

Failed to delete local thread data.

Error message

Failed to delete local thread data.

What it means

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.

Source

Thrown at frontend/src/core/threads/hooks.ts:3079

async function deleteLocalThreadData(threadId: string) {
  const response = await fetch(
    `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}`,
    {
      method: "DELETE",
    },
  );

  // A 404 means the thread is already gone — the desired end state. The prior
  // `apiClient.threads.delete` call hits the same gateway handler (nginx
  // rewrites /api/langgraph/threads/* to /api/threads/*) and removes the
  // thread_meta row, so this second delete's ownership guard 404s. Treat it as
  // success to keep the delete idempotent.
  if (!response.ok && response.status !== 404) {
    const error = await response
      .json()
      .catch(() => ({ detail: "Failed to delete local thread data." }));
    throw new Error(error.detail ?? "Failed to delete local thread data.");
  }
}

async function deleteThreadEverywhere(
  apiClient: ThreadDeleteClient,
  threadId: string,
) {
  await apiClient.threads.delete(threadId);
  await deleteLocalThreadData(threadId);
}

export async function findSidecarThreadIdsForParent(
  apiClient: ThreadSidecarSearchClient,
  parentThreadId: string,
) {
  const threadIds: string[] = [];
  const limit = 100;
  let offset = 0;

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Check which status the local-thread-data DELETE returned (only 404 is whitelisted).
  2. 401/403: re-auth and retry the delete from the UI.
  3. 5xx: inspect gateway logs for the local data delete handler; verify the thread storage backend is reachable.
  4. If the endpoint is legitimately optional in your deployment, treat additional statuses explicitly rather than widening the 404 special case blindly.

Example fix

// before: only 404 tolerated
if (!response.ok && response.status !== 404) { throw new Error(...); }

// after: also tolerate auth-redirect HTML bodies for clearer errors
if (!response.ok && response.status !== 404) {
  const body = await response.json().catch(() => null);
  throw new Error(body?.detail ?? `Failed to delete local thread data (HTTP ${response.status}).`);
}
Defensive patterns

Strategy: fallback

Validate before calling

// Before deleteThreadEverywhere, optionally check thread existence; main guard is server-side idempotency which already whitelists 404.

Try / catch

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; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/685812c5981d102c. Report an issue: GitHub.