{"record":{"id":"9d002df57ecc6ebb","repo":"mastra-ai/mastra","slug":"cannot-merge-a-knowledge-node-into-itself","errorCode":null,"errorMessage":"Cannot merge a knowledge node into itself","messagePattern":"Cannot merge a knowledge node into itself","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/storage/domains/knowledge/inmemory.ts","lineNumber":250,"sourceCode":"    if (input.content !== undefined || input.name !== undefined || input.scope !== undefined) {\n      this.#replaceMentions('node', input.id, updated.content ?? '', input.resolutionScope ?? scope, scope);\n    }\n    this.#recordActivity('node-updated', 'node', input.id, scope);\n    const scopeChanged = knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope);\n    if (scopeChanged) {\n      this.#enqueue('node', input.id, 'delete', createKnowledgeUlid(), existing.scope);\n      for (const record of this.#db.knowledgeRecords.values()) {\n        if (record.node !== input.id) continue;\n        this.#enqueue('record', record.id, 'delete', createKnowledgeUlid(), record.scope);\n        if (!record.deletedAt) this.#enqueue('record', record.id, 'upsert', createKnowledgeUlid(), record.scope);\n      }\n    }\n    this.#enqueue('node', input.id, 'upsert', updated.version, scope);\n    return cloneNode(updated);\n  }\n\n  async mergeNodes(input: { sourceId: string; targetId: string; sourceVersion: number }): Promise<KnowledgeNode> {\n    if (input.sourceId === input.targetId) throw new Error('Cannot merge a knowledge node into itself');\n    const source = this.#db.knowledgeNodes.get(input.sourceId);\n    if (!source) throw new KnowledgeNotFoundError('node', input.sourceId);\n    if (source.version !== input.sourceVersion) throw new KnowledgeConflictError(input.sourceId);\n    const target = this.#resolveTerminalNode(input.targetId);\n    if (!target) throw new KnowledgeNotFoundError('node', input.targetId);\n    if (target.id === source.id) throw new Error('Cannot create a knowledge merge cycle');\n    if (!isKnowledgeScopeVisible(target.scope, source.scope)) {\n      throw new Error('Cannot merge a knowledge node into a target that is narrower than its source scope');\n    }\n\n    for (const [id, record] of this.#db.knowledgeRecords) {\n      if (record.node === source.id) {\n        this.#db.knowledgeRecords.set(id, { ...record, node: target.id });\n        this.#enqueue('record', id, record.deletedAt ? 'delete' : 'upsert', createKnowledgeUlid(), record.scope);\n      }\n    }\n    for (const [key, mentions] of this.#db.knowledgeMentions) {\n      if (mentions.has(source.id)) {","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/storage/domains/knowledge/inmemory.ts#L232-L268","documentation":"mergeNodes was called with sourceId === targetId. Merging a node into itself is meaningless and would corrupt the merge chain, so the store rejects it immediately before any lookup.","triggerScenarios":"Calling mergeNodes({ sourceId: x, targetId: x, sourceVersion }) — usually from code that resolved target and source from the same variable, or a UI allowing the same node to be selected for both fields.","commonSituations":"Batch dedup scripts where the duplicate list accidentally includes the node itself; frontend dropdown not excluding the source node.","solutions":["Guard the call site: skip merging when sourceId === targetId","When deduplicating, exclude the source node id from candidate targets","Fetch candidate targets by name and filter out the source before merging"],"exampleFix":"// before\nawait store.mergeNodes({ sourceId, targetId, sourceVersion });\n// after\nif (sourceId !== targetId) await store.mergeNodes({ sourceId, targetId, sourceVersion });","handlingStrategy":"validation","validationCode":"if (sourceId === targetId) throw new Error('refusing to merge a node into itself');","typeGuard":"function isMergePairValid(sourceId: string, targetId: string): boolean {\n  return sourceId !== targetId && sourceId.length > 0 && targetId.length > 0;\n}","tryCatchPattern":"try {\n  await store.mergeNodes({ sourceId, targetId, sourceVersion });\n} catch (e) {\n  if (e instanceof Error && e.message === 'Cannot merge a knowledge node into itself') return null;\n  throw e;\n}","preventionTips":["Exclude the source id from candidate target lists in dedup UIs and scripts","Validate merge pairs before invoking mergeNodes","When targets come from name lookups, filter out the source node by id","Add unit tests for self-merge guard rails in dedup logic"],"tags":["knowledge-store","merge","invalid-argument","self-reference"],"backgroundTag":"self-referential-merge","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}