bytedance/deer-flow · error

Failed to branch conversation.

Error message

Failed to branch conversation.

What it means

Thrown when POST /api/threads/{id}/branches (branch a conversation from one or more turns) returns non-2xx. readThreadAPIError layers the backend detail over the fallback text. The backend validates the referenced message_ids and thread state: 404 unknown thread or message, 409 branching a thread mid-run, 422 empty/malformed message id list.

Source

Thrown at frontend/src/core/threads/api.ts:105

  input: BranchThreadFromTurnInput,
): Promise<ThreadBranchResponse> {
  const response = await fetchWithAuth(
    `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/branches`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        message_id: input.messageId,
        message_ids: input.messageIds ?? [input.messageId],
        ...(input.title ? { title: input.title } : {}),
      }),
    },
  );

  if (!response.ok) {
    throw new Error(
      await readThreadAPIError(response, "Failed to branch conversation."),
    );
  }

  return (await response.json()) as ThreadBranchResponse;
}

export async function patchThreadMetadata(
  threadId: string,
  metadata: ThreadMetadataPatch,
): Promise<ThreadMetadataPatchResponse> {
  const response = await fetchWithAuth(
    `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}`,
    {
      method: "PATCH",
      headers: {
        "Content-Type": "application/json",
      },

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Read the wrapped detail message — it names whether thread or message was not found
  2. Disable the branch action while a run is streaming; retry after completion
  3. Refresh the thread's message list before branching so ids are current
  4. Guard against double-submit by disabling the button during the request

Example fix

// before
const branch = await branchThreadFromTurn(threadId, {messageId});

// after
const branch = await branchThreadFromTurn(threadId, {messageId}).catch((e) => {
  if (/not found/i.test(e.message)) {
    await refreshMessages(threadId);
    return null; // message gone; user re-picks a turn
  }
  throw e;
});
Defensive patterns

Strategy: try-catch

Validate before calling

function branchInputValid(input: BranchThreadFromTurnInput, validIds: Set<string>): boolean {
  return validIds.has(input.messageId) && (input.messageIds ?? [input.messageId]).every((id) => validIds.has(id));
}

Type guard

export function isThreadBranchError(e: unknown): e is Error {
  return e instanceof Error && e.message.includes('Failed to branch conversation.');
}

Try / catch

try {
  return await branchThreadFromTurn(threadId, input);
} catch (e) {
  if (isThreadBranchError(e) && /not found/i.test(e.message)) {
    await refreshThread(threadId);
    return null; // caller re-selects a turn
  }
  throw e;
}

Prevention

When it happens

Trigger: Branching from a message that was deleted or belongs to another thread; branching while an agent run is active on the source thread; sending message_ids that don't share a consistent seq ordering.

Common situations: User clicks 'branch from here' on an old message after history was compacted (message ids no longer resolvable); double-click creating two branch requests; branching from a turn still streaming.

Related errors


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