bytedance/deer-flow · warning · HTTPException
Branching is only available in the main conversation.
Error message
Branching is only available in the main conversation.
What it means
409 from POST /threads/{thread_id}/branches when the source thread's metadata carries the internal sidecar marker (_SIDECAR_METADATA_KEY is True). Sidecar threads (sub-agent/task-scoped conversations) are not part of the main conversation, and branching them is intentionally blocked.
Source
Thrown at backend/app/gateway/routers/threads.py:800
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:
raise HTTPException(status_code=409, detail="This turn can no longer be branched from.")
replay_base_tuple = await _find_branch_replay_base(
source_accessor,
source_config,View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Branch only from main conversation threads; verify via GET /threads/{id} that metadata does not contain the sidecar key before offering the branch action.
- Filter sidecar threads out of any client-side thread listing (they are tagged in metadata).
- If you truly need a copy of a sidecar conversation, export its messages instead of branching.
Example fix
// before
const res = await fetch(`/api/threads/${id}/branches`, {method: 'POST', ...}); // 409 on sidecar threads
// after
const meta = (await (await fetch(`/api/threads/${id}`)).json()).metadata;
const branchable = !meta?.[__SIDECAR_KEY__];
if (branchable) { /* show branch UI */ } Defensive patterns
Strategy: validation
Validate before calling
const {metadata} = await api.get(`/api/threads/${id}`);
const isSidecar = Object.values(metadata || {}).includes(true) && metadata['__sidecar__'] === true; // key is internal; simplest: only branch threads your UI created as main conversations Try / catch
try { await api.post(`/api/threads/${id}/branches`, body); }
catch (err) { if (err.status === 409 && /main conversation/.test(err.detail)) hideBranchOption(id); else throw err; } Prevention
- Filter internal/sidecar threads out of user-facing thread lists.
- Treat 409 'main conversation' as permanent for that thread — don't retry.
When it happens
Trigger: Calling the branch endpoint with the id of a sidecar thread — e.g. a subagent's isolated conversation thread created by the harness — whose metadata was stamped with the sidecar key at creation.
Common situations: UI surfacing internal sidecar threads in the thread list; scripts enumerating all thread ids from the store and calling branch on each; copying a thread id from run-event payloads that reference a sidecar conversation.
Related errors
- Thread has a run in flight. Set the goal after the run finis
- Failed to create side conversation.
- Failed to branch conversation.
- Agent '{normalized_name}' already exists
- Thread has a run in flight. Save after the run finishes.
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/37366391b298e709.
Report an issue: GitHub.