{"record":{"id":"6844f3fe497b6bbd","repo":"ruvnet/ruflo","slug":"cannot-add-a-sibling-to-a-legacy-conversation","errorCode":null,"errorMessage":"Cannot add a sibling to a legacy conversation","messagePattern":"Cannot add a sibling to a legacy conversation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/src/lib/utils/tree/addSibling.ts","lineNumber":9,"sourceCode":"import { v4 } from \"uuid\";\nimport type { Tree, TreeId, NewNode, TreeNode } from \"./tree\";\n\nexport function addSibling<T>(conv: Tree<T>, message: NewNode<T>, siblingId: TreeId): TreeId {\n\tif (conv.messages.length === 0) {\n\t\tthrow new Error(\"Cannot add a sibling to an empty conversation\");\n\t}\n\tif (!conv.rootMessageId) {\n\t\tthrow new Error(\"Cannot add a sibling to a legacy conversation\");\n\t}\n\n\tconst sibling = conv.messages.find((m) => m.id === siblingId);\n\n\tif (!sibling) {\n\t\tthrow new Error(\"The sibling message doesn't exist\");\n\t}\n\n\tif (!sibling.ancestors || sibling.ancestors?.length === 0) {\n\t\tthrow new Error(\"The sibling message is the root message, therefore we can't add a sibling\");\n\t}\n\n\tconst messageId = v4();\n\n\tconv.messages.push({\n\t\t...message,\n\t\tid: messageId,\n\t\tancestors: sibling.ancestors,","sourceCodeStart":1,"sourceCodeEnd":27,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/ruflo/src/ruvocal/src/lib/utils/tree/addSibling.ts#L1-L27","documentation":"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.","triggerScenarios":"addSibling(conv, message, siblingId) where conv.rootMessageId is undefined — any alternative/branch action attempted on old-format data.","commonSituations":"Users reopening very old conversations and clicking 'edit message as new alternative'; bulk-replaying branching operations over imported legacy history.","solutions":["On legacy conversations, offer append-only flows (addChildren to the last message) instead of branching.","Migrate the conversation to the tree format (assign rootMessageId and ancestors per node) to unlock sibling operations.","Detect legacy format early in the UI (no rootMessageId) and disable branch affordances."],"exampleFix":"// before\naddSibling(conv, message, siblingId); // throws on legacy conv\n\n// after\nif (!conv.rootMessageId) {\n\taddChildren(conv, message, conv.messages[conv.messages.length - 1].id);\n} else {\n\taddSibling(conv, message, siblingId);\n}","handlingStrategy":"type-guard","validationCode":"if (!conv.rootMessageId) {\n\tthrow new Error(\"branching unsupported on legacy conversation\");\n}\naddSibling(conv, message, siblingId);","typeGuard":"function isTreeConversation(conv: { rootMessageId?: string }): conv is { rootMessageId: string } {\n\treturn typeof conv.rootMessageId === \"string\" && conv.rootMessageId.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Migrate legacy conversations on read: assign rootMessageId and ancestors before use.","Feature-flag branch UI off for legacy data.","Version your persisted conversation schema so format checks are explicit."],"tags":["tree","conversation","legacy-data","migration"],"backgroundTag":"invalid-tree-operation","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}