ruvnet/ruflo · error

Cannot add a sibling to a legacy conversation

Error message

Cannot add a sibling to a legacy conversation

What it means

Thrown by addSibling() when the conversation has messages but no rootMessageId — a legacy, pre-tree conversation. Sibling insertion requires re-hanging the new node under the sibling's parent using ancestor chains, which legacy conversations do not store, so the operation is unsupported on them.

Solutions

  1. On legacy conversations, offer append-only flows (addChildren to the last message) instead of branching.
  2. Migrate the conversation to the tree format (assign rootMessageId and ancestors per node) to unlock sibling operations.
  3. Detect legacy format early in the UI (no rootMessageId) and disable branch affordances.

Example fix

// before
addSibling(conv, message, siblingId); // throws on legacy conv

// after
if (!conv.rootMessageId) {
	addChildren(conv, message, conv.messages[conv.messages.length - 1].id);
} else {
	addSibling(conv, message, siblingId);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!conv.rootMessageId) {
	throw new Error("branching unsupported on legacy conversation");
}
addSibling(conv, message, siblingId);

Type guard

function isTreeConversation(conv: { rootMessageId?: string }): conv is { rootMessageId: string } {
	return typeof conv.rootMessageId === "string" && conv.rootMessageId.length > 0;
}

Prevention

When it happens

Trigger: addSibling(conv, message, siblingId) where conv.rootMessageId is undefined — any alternative/branch action attempted on old-format data.

Common situations: Users reopening very old conversations and clicking 'edit message as new alternative'; bulk-replaying branching operations over imported legacy history.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/6844f3fe497b6bbd. Report an issue: GitHub.

Appendix: source

Thrown at ruflo/src/ruvocal/src/lib/utils/tree/addSibling.ts:9

import { v4 } from "uuid";
import type { Tree, TreeId, NewNode, TreeNode } from "./tree";

export function addSibling<T>(conv: Tree<T>, message: NewNode<T>, siblingId: TreeId): TreeId {
	if (conv.messages.length === 0) {
		throw new Error("Cannot add a sibling to an empty conversation");
	}
	if (!conv.rootMessageId) {
		throw new Error("Cannot add a sibling to a legacy conversation");
	}

	const sibling = conv.messages.find((m) => m.id === siblingId);

	if (!sibling) {
		throw new Error("The sibling message doesn't exist");
	}

	if (!sibling.ancestors || sibling.ancestors?.length === 0) {
		throw new Error("The sibling message is the root message, therefore we can't add a sibling");
	}

	const messageId = v4();

	conv.messages.push({
		...message,
		id: messageId,
		ancestors: sibling.ancestors,

View on GitHub (pinned to fa13ee4ad6)