{"record":{"id":"37366391b298e709","repo":"bytedance/deer-flow","slug":"branching-is-only-available-in-the-main-conversati","errorCode":null,"errorMessage":"Branching is only available in the main conversation.","messagePattern":"Branching is only available in the main conversation\\.","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"backend/app/gateway/routers/threads.py","lineNumber":800,"sourceCode":"        metadata=body.metadata,\n    )\n\n\n@router.post(\"/{thread_id}/branches\", response_model=ThreadBranchResponse)\n@require_permission(\"threads\", \"write\", owner_check=True, require_existing=True)\nasync def branch_thread(thread_id: ThreadId, body: ThreadBranchRequest, request: Request) -> ThreadBranchResponse:\n    \"\"\"Create a new main-thread branch from a completed assistant turn.\"\"\"\n    from app.gateway.deps import get_thread_store\n\n    thread_store = get_thread_store(request)\n\n    source_record = await thread_store.get(thread_id)\n    if source_record is None:\n        raise HTTPException(status_code=404, detail=f\"Thread {thread_id} not found\")\n\n    source_metadata = source_record.get(\"metadata\") or {}\n    if source_metadata.get(_SIDECAR_METADATA_KEY) is True:\n        raise HTTPException(status_code=409, detail=\"Branching is only available in the main conversation.\")\n    source_accessor, source_config = build_checkpoint_state_accessor(\n        request,\n        thread_id=thread_id,\n        assistant_id=source_record.get(\"assistant_id\"),\n    )\n\n    target_message_ids = {body.message_id, *body.message_ids}\n    snapshot = await _find_branch_checkpoint(source_accessor, source_config, target_message_ids)\n    parent_checkpoint_id = _checkpoint_id(snapshot)\n    if not parent_checkpoint_id:\n        raise HTTPException(status_code=409, detail=\"This turn can no longer be branched from.\")\n    target_human = _branch_target_human_message(_checkpoint_messages(snapshot), target_message_ids)\n    target_human_id = _message_id(target_human)\n    if not target_human_id:\n        raise HTTPException(status_code=409, detail=\"This turn can no longer be branched from.\")\n    replay_base_tuple = await _find_branch_replay_base(\n        source_accessor,\n        source_config,","sourceCodeStart":782,"sourceCodeEnd":818,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/threads.py#L782-L818","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst res = await fetch(`/api/threads/${id}/branches`, {method: 'POST', ...}); // 409 on sidecar threads\n// after\nconst meta = (await (await fetch(`/api/threads/${id}`)).json()).metadata;\nconst branchable = !meta?.[__SIDECAR_KEY__];\nif (branchable) { /* show branch UI */ }","handlingStrategy":"validation","validationCode":"const {metadata} = await api.get(`/api/threads/${id}`);\nconst isSidecar = Object.values(metadata || {}).includes(true) && metadata['__sidecar__'] === true; // key is internal; simplest: only branch threads your UI created as main conversations","typeGuard":null,"tryCatchPattern":"try { await api.post(`/api/threads/${id}/branches`, body); }\ncatch (err) { if (err.status === 409 && /main conversation/.test(err.detail)) hideBranchOption(id); else throw err; }","preventionTips":["Filter internal/sidecar threads out of user-facing thread lists.","Treat 409 'main conversation' as permanent for that thread — don't retry."],"tags":["threads","branching","sidecar","http-409","conflict"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}