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

  1. Branch only from main conversation threads; verify via GET /threads/{id} that metadata does not contain the sidecar key before offering the branch action.
  2. Filter sidecar threads out of any client-side thread listing (they are tagged in metadata).
  3. 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

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


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