ruvnet/ruflo · error · 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 conv.rootMessageId is falsy — the legacy shape. addSibling needs the tree's ancestor links to attach the new node as a branch off the sibling's parent, which a flat legacy list does not have, so the operation is refused outright (unlike addChildren, which tolerates append-to-tail on legacy data).

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 6b01dc5a68)

Solutions

  1. Convert the conversation with convertLegacyConversation before exposing sibling/branch actions.
  2. Backfill rootMessageId + ancestors for legacy records in bulk.
  3. Hide the "regenerate / branch" UI affordance for conversations where !conv.rootMessageId until migration completes.

Example fix

// before
addSibling(legacyConv, message, siblingId); // throws
// after
import { convertLegacyConversation } from "$lib/utils/tree/convertLegacyConversation";
const treeConv = !legacyConv.rootMessageId ? convertLegacyConversation(legacyConv) : legacyConv;
addSibling(treeConv, message, siblingId);
Defensive patterns

Strategy: validation

Validate before calling

import { convertLegacyConversation } from "$lib/utils/tree/convertLegacyConversation";
if (!conv.rootMessageId && conv.messages.length > 0) {
  conv = convertLegacyConversation(conv);
}
addSibling(conv, message, siblingId);

Type guard

function isLegacyConversation<T>(conv: Tree<T>): boolean {
  return conv.messages.length > 0 && !conv.rootMessageId;
}

Try / catch

try {
  addSibling(conv, message, siblingId);
} catch (e) {
  if (String((e as Error)?.message).includes("legacy conversation")) {
    addSibling(convertLegacyConversation(conv), message, siblingId);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling addSibling (e.g. a "regenerate this response" action) on a conversation loaded from old data that was never migrated to the tree schema.

Common situations: Pre-migration records in the DB; importing conversations from another system as a flat message list; an older deployment that wrote messages without rootMessageId.

Related errors


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