bytedance/deer-flow · warning · HTTPException
Thread {thread_id} not found
Error message
Thread {thread_id} not found What it means
404 from POST /api/threads/{thread_id}/branches when thread_store.get(thread_id) returns None — no thread_meta record exists for the source thread. The endpoint requires a persisted main-conversation thread before it can branch from one of its turns.
Source
Thrown at backend/app/gateway/routers/threads.py:796
thread_id=thread_id,
status="idle",
created_at=now,
updated_at=now,
metadata=body.metadata,
)
@router.post("/{thread_id}/branches", response_model=ThreadBranchResponse)
@require_permission("threads", "write", owner_check=True, require_existing=True)
async def branch_thread(thread_id: ThreadId, body: ThreadBranchRequest, request: Request) -> ThreadBranchResponse:
"""Create a new main-thread branch from a completed assistant turn."""
from app.gateway.deps import get_thread_store
thread_store = get_thread_store(request)
source_record = await thread_store.get(thread_id)
if source_record is None:
raise HTTPException(status_code=404, detail=f"Thread {thread_id} not found")
source_metadata = source_record.get("metadata") or {}
if source_metadata.get(_SIDECAR_METADATA_KEY) is True:
raise HTTPException(status_code=409, detail="Branching is only available in the main conversation.")
source_accessor, source_config = build_checkpoint_state_accessor(
request,
thread_id=thread_id,
assistant_id=source_record.get("assistant_id"),
)
target_message_ids = {body.message_id, *body.message_ids}
snapshot = await _find_branch_checkpoint(source_accessor, source_config, target_message_ids)
parent_checkpoint_id = _checkpoint_id(snapshot)
if not parent_checkpoint_id:
raise HTTPException(status_code=409, detail="This turn can no longer be branched from.")
target_human = _branch_target_human_message(_checkpoint_messages(snapshot), target_message_ids)
target_human_id = _message_id(target_human)
if not target_human_id:View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Confirm the thread exists: GET /api/threads/{thread_id} returns 200 before branching.
- If it was deleted, re-create it or branch from an existing thread.
- Check you are authenticated as the thread's owner user when the store is user-scoped.
- Regenerate/repair the client's thread list so it only offers branchable ids.
Example fix
// before
await fetch(`/api/threads/${staleId}/branches`, {method: 'POST', body: JSON.stringify({message_id: id})});
// after
const exists = await fetch(`/api/threads/${staleId}`).ok;
if (!exists) { /* refresh thread list, pick a live thread */ }
await fetch(`/api/threads/${staleId}/branches`, {method: 'POST', body: JSON.stringify({message_id: id})}); Defensive patterns
Strategy: validation
Validate before calling
const res = await fetch(`/api/threads/${id}`);
if (res.status === 404) { /* drop branch affordance; refresh thread list */ } Try / catch
try { await api.post(`/api/threads/${id}/branches`, {message_id}); }
catch (err) { if (err.status === 404) removeThreadFromUI(id); else throw err; } Prevention
- Only show the branch action for threads returned by the thread list API.
- Re-verify existence after tab regained focus or reconnect (thread may have been deleted).
When it happens
Trigger: Branching a thread_id that was never created via POST /threads, was deleted, belongs to another owner (store scoped by user), or exists only as checkpoints with no thread_meta row.
Common situations: Frontend keeps a stale thread id after the thread was deleted or its ownership changed; calling the branch API against a sidecar/generated thread that never got a meta record; typos or truncated UUIDs in the URL.
Related errors
- Failed to branch conversation.
- Thread {thread_id} not found
- Agent '{name}' not found
- Artifact not found: {path}
- Skill file not found: {skill_file_path}
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/07a0c97c5f13b4d7.
Report an issue: GitHub.