ruvnet/ruflo · error

This is a legacy conversation, you can only append to the…

Error message

This is a legacy conversation, you can only append to the last message

What it means

Thrown by addChildren() for legacy conversations — trees with messages but no rootMessageId, predating the ancestor-tracking format. Legacy trees carry no parent links, so insertion is only possible at the tail; passing a parentId that is not the id of the last message would orphan the node, hence the guard.

Solutions

  1. Append only to the last message of a legacy conversation: pass conv.messages[conv.messages.length - 1].id (or no parentId when it is empty).
  2. To branch anywhere, migrate the conversation to the tree format first (set rootMessageId and rebuild ancestors for each node).
  3. If legacy conversations are irrelevant in your deployment, archive or drop them.
  4. Write a one-time migration that walks messages in order and assigns rootMessageId + ancestors so addChildren gains full branching.

Example fix

// before (legacy conv, branching mid-history)
addChildren(conv, message, someOldMessageId); // throws

// after
const lastId = conv.messages[conv.messages.length - 1].id;
addChildren(conv, message, lastId);
Defensive patterns

Strategy: type-guard

Validate before calling

const isLegacy = !conv.rootMessageId;
if (isLegacy && parentId !== conv.messages[conv.messages.length - 1].id) {
	throw new Error("legacy conversation supports append-only");
}

Type guard

function isLegacyConversation<T>(conv: { rootMessageId?: string }): conv is { rootMessageId?: undefined } {
	return conv.rootMessageId === undefined;
}

Prevention

When it happens

Trigger: conv.rootMessageId is undefined (old persisted conversation) and addChildren is called with a parentId different from conv.messages[conv.messages.length - 1].id — i.e., trying to branch off an arbitrary historical message.

Common situations: Reopening old conversations created before the tree migration and attempting to edit/branch a middle message; importing legacy data and replaying edit operations against it.

Related errors


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

Appendix: source

Thrown at ruflo/src/ruvocal/src/lib/utils/tree/addChildren.ts:25

		const messageId = v4();
		conv.rootMessageId = messageId;
		conv.messages.push({
			...message,
			ancestors: [],
			id: messageId,
		} as TreeNode<T>);
		return messageId;
	}

	if (!parentId) {
		throw new Error("You need to specify a parentId if this is not the first message");
	}

	const messageId = v4();
	if (!conv.rootMessageId) {
		// if there is no parentId we just push the message
		if (!!parentId && parentId !== conv.messages[conv.messages.length - 1].id) {
			throw new Error("This is a legacy conversation, you can only append to the last message");
		}
		conv.messages.push({ ...message, id: messageId } as TreeNode<T>);
		return messageId;
	}

	const ancestors = [...(conv.messages.find((m) => m.id === parentId)?.ancestors ?? []), parentId];
	conv.messages.push({
		...message,
		ancestors,
		id: messageId,
		children: [],
	} as TreeNode<T>);

	const parent = conv.messages.find((m) => m.id === parentId);

	if (parent) {
		if (parent.children) {
			parent.children.push(messageId);

View on GitHub (pinned to fa13ee4ad6)