ruvnet/ruflo · error

Message not found

Error message

Message not found

What it means

Thrown by buildSubtree() when asked to materialize the message chain up to a given id but no message with that id exists in the conversation. For tree conversations the target lookup and each ancestor lookup can both fail ('Message not found' vs 'Ancestor not found'); for legacy conversations the id must appear in the flat message array.

Solutions

  1. Validate the id against the current conversation before building: conv.messages.some((m) => m.id === id).
  2. For share/permalink pages, fall back to the whole conversation or a 404 when the target was deleted.
  3. Refresh the conversation from the server before operating on user-supplied ids.
  4. Fix data integrity if ancestors reference missing nodes — the sibling 'Ancestor not found' error indicates dangling ancestor ids.

Example fix

// before
const nodes = buildSubtree(conv, messageId);

// after
if (!conv.messages.some((m) => m.id === messageId)) {
	throw new Error("message no longer exists");
}
const nodes = buildSubtree(conv, messageId);
Defensive patterns

Strategy: validation

Validate before calling

if (!conv.messages.some((m) => m.id === messageId)) {
	throw new Error(`message ${messageId} not found in conversation`);
}
const subtree = buildSubtree(conv, messageId);

Type guard

function hasMessage(conv: { messages: { id: string }[] }, id: string): boolean {
	return conv.messages.some((m) => m.id === id);
}

Try / catch

try {
	const nodes = buildSubtree(conv, messageId);
} catch (err) {
	if (String(err) === "Message not found" || String(err) === "Ancestor not found") {
		return new Response("Message no longer exists", { status: 404 });
	}
	throw err;
}

Prevention

When it happens

Trigger: buildSubtree(conv, id) with an id that is absent from conv.messages: deleted message, id from another conversation, or a client holding a stale tree snapshot after concurrent edits.

Common situations: Rendering a permalink/share link whose target message was deleted afterwards; navigating to ?message=<id> after the conversation was trimmed; concurrent sessions where one deletes messages while another builds subtrees.

Related errors


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

Appendix: source

Thrown at ruflo/src/ruvocal/src/lib/utils/tree/buildSubtree.ts:8

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

export function buildSubtree<T>(conv: Tree<T>, id: TreeId): TreeNode<T>[] {
	if (!conv.rootMessageId) {
		if (conv.messages.length === 0) return [];
		// legacy conversation slice up to id
		const index = conv.messages.findIndex((m) => m.id === id);
		if (index === -1) throw new Error("Message not found");
		return conv.messages.slice(0, index + 1);
	} else {
		// find the message with the right id then create the ancestor tree
		const message = conv.messages.find((m) => m.id === id);
		if (!message) throw new Error("Message not found");

		return [
			...(message.ancestors?.map((ancestorId) => {
				const ancestor = conv.messages.find((m) => m.id === ancestorId);
				if (!ancestor) throw new Error("Ancestor not found");
				return ancestor;
			}) ?? []),
			message,
		];
	}
}

View on GitHub (pinned to fa13ee4ad6)