{"record":{"id":"f131402962aa1522","repo":"ruvnet/ruflo","slug":"this-is-a-legacy-conversation-you-can-only-append","errorCode":null,"errorMessage":"This is a legacy conversation, you can only append to the last message","messagePattern":"This is a legacy conversation, you can only append to the last message","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/src/lib/utils/tree/addChildren.ts","lineNumber":25,"sourceCode":"\t\tconst messageId = v4();\n\t\tconv.rootMessageId = messageId;\n\t\tconv.messages.push({\n\t\t\t...message,\n\t\t\tancestors: [],\n\t\t\tid: messageId,\n\t\t} as TreeNode<T>);\n\t\treturn messageId;\n\t}\n\n\tif (!parentId) {\n\t\tthrow new Error(\"You need to specify a parentId if this is not the first message\");\n\t}\n\n\tconst messageId = v4();\n\tif (!conv.rootMessageId) {\n\t\t// if there is no parentId we just push the message\n\t\tif (!!parentId && parentId !== conv.messages[conv.messages.length - 1].id) {\n\t\t\tthrow new Error(\"This is a legacy conversation, you can only append to the last message\");\n\t\t}\n\t\tconv.messages.push({ ...message, id: messageId } as TreeNode<T>);\n\t\treturn messageId;\n\t}\n\n\tconst ancestors = [...(conv.messages.find((m) => m.id === parentId)?.ancestors ?? []), parentId];\n\tconv.messages.push({\n\t\t...message,\n\t\tancestors,\n\t\tid: messageId,\n\t\tchildren: [],\n\t} as TreeNode<T>);\n\n\tconst parent = conv.messages.find((m) => m.id === parentId);\n\n\tif (parent) {\n\t\tif (parent.children) {\n\t\t\tparent.children.push(messageId);","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/ruflo/src/ruvocal/src/lib/utils/tree/addChildren.ts#L7-L43","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","To branch anywhere, migrate the conversation to the tree format first (set rootMessageId and rebuild ancestors for each node).","If legacy conversations are irrelevant in your deployment, archive or drop them.","Write a one-time migration that walks messages in order and assigns rootMessageId + ancestors so addChildren gains full branching."],"exampleFix":"// before (legacy conv, branching mid-history)\naddChildren(conv, message, someOldMessageId); // throws\n\n// after\nconst lastId = conv.messages[conv.messages.length - 1].id;\naddChildren(conv, message, lastId);","handlingStrategy":"type-guard","validationCode":"const isLegacy = !conv.rootMessageId;\nif (isLegacy && parentId !== conv.messages[conv.messages.length - 1].id) {\n\tthrow new Error(\"legacy conversation supports append-only\");\n}","typeGuard":"function isLegacyConversation<T>(conv: { rootMessageId?: string }): conv is { rootMessageId?: undefined } {\n\treturn conv.rootMessageId === undefined;\n}","tryCatchPattern":null,"preventionTips":["Run a data migration that backfills rootMessageId and ancestors for old conversations.","Disable mid-history branching UI when isLegacyConversation is true.","Never assume persisted conversations are in the current format — check rootMessageId first."],"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"}