{"record":{"id":"7d8dff336a81cf50","repo":"mastra-ai/mastra","slug":"duplicate-extractor-slug-extractor-slug-from","errorCode":null,"errorMessage":"Duplicate extractor slug \"${extractor.slug}\" from \"${previous}\" and \"${extractor.name}\".","messagePattern":"Duplicate extractor slug \"(.+?)\" from \"(.+?)\" and \"(.+?)\"\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/memory/src/processors/observational-memory/extractor.ts","lineNumber":226,"sourceCode":"}\n\nexport async function resolveExtractors(\n  extractors: readonly Extractor<any>[],\n  context: ExtractorRuntimeContext,\n): Promise<Extractor<any>[]> {\n  return Promise.all(extractors.map(extractor => extractor.resolve(context)));\n}\n\nexport function validateExtractorList(extractors: readonly Extractor<any>[]): Extractor<any>[] {\n  const seen = new Map<string, string>();\n  for (const extractor of extractors) {\n    assertValidSlug(extractor.slug, extractor.name);\n    if (!extractor.internal && RESERVED_XML_TAGS.has(extractor.slug)) {\n      throw new Error(`Extractor slug \"${extractor.slug}\" is reserved by Observational Memory.`);\n    }\n    const previous = seen.get(extractor.slug);\n    if (previous) {\n      throw new Error(`Duplicate extractor slug \"${extractor.slug}\" from \"${previous}\" and \"${extractor.name}\".`);\n    }\n    seen.set(extractor.slug, extractor.name);\n  }\n  return [...extractors];\n}\n\nfunction isJsonLike(value: string): boolean {\n  return /^(?:[\\[{\"-]|\\d|true\\b|false\\b|null\\b)/.test(value.trim());\n}\n\nfunction candidateValues(raw: string): unknown[] {\n  const trimmed = raw.trim();\n  const candidates: unknown[] = [];\n  const add = (value: unknown) => {\n    if (!candidates.some(candidate => Object.is(candidate, value))) {\n      candidates.push(value);\n    }\n  };","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/processors/observational-memory/extractor.ts#L208-L244","documentation":"`validateExtractorList` enforces that every extractor in a composed list has a unique slug, since slugs become XML tags and metadata keys (`extracted.<slug>`); duplicates would make parsed values and persistence ambiguous. When two extractors resolve to the same slug, the error reports both conflicting names.","triggerScenarios":"Two extractors whose names slugify identically (e.g. 'User Goals' and 'user-goals!' both → 'user-goals'); adding the same Extractor instance twice to the list; dynamically generating extractors with names that collide after slugification.","commonSituations":"Case/punctuation differences in names that feel distinct but slugify the same; merging extractor arrays from multiple sources (defaults + user config) that both include the same extractor; copy-pasted extractor definitions with only name-case changed.","solutions":["Rename one extractor so each slug is unique.","Deduplicate the extractor array before passing it to the processor (e.g. by slug via slugifyExtractorName).","If merging config sources, prefer user-supplied extractors over defaults with the same slug instead of concatenating both."],"exampleFix":"// before\n[new Extractor({ name: 'User Goals', ... }), new Extractor({ name: 'user-goals!', ... })]\n// after\n[new Extractor({ name: 'User Goals', ... }), new Extractor({ name: 'User Milestones', ... })]","handlingStrategy":"validation","validationCode":"function dedupeBySlug(extractors) {\n  const seen = new Map();\n  return extractors.filter(e => {\n    if (seen.has(e.slug)) return false;\n    seen.set(e.slug, e.name);\n    return true;\n  });\n}","typeGuard":"function hasUniqueSlugs(list) {\n  return new Set(list.map(e => e.slug)).size === list.length;\n}","tryCatchPattern":"try {\n  validateExtractorList(extractors);\n} catch (e) {\n  if (e.message.includes('Duplicate extractor slug')) {\n    logger.error('Duplicate extractor slugs in list', { msg: e.message });\n  } else { throw e; }\n}","preventionTips":["Deduplicate merged extractor lists by slug before composition.","When merging defaults with user config, let user entries override same-slug defaults instead of concatenating.","Normalize names early (lowercase, strip punctuation) so accidental collisions are visible in code review."],"tags":["naming","duplicate-key","observational-memory"],"backgroundTag":"duplicate-key-conflict","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}