mastra-ai/mastra · error · Error

Duplicate extractor slug "${extractor.slug}" from "${previou

Error message

Duplicate extractor slug "${extractor.slug}" from "${previous}" and "${extractor.name}".

What it means

`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.

Source

Thrown at packages/memory/src/processors/observational-memory/extractor.ts:226

}

export async function resolveExtractors(
  extractors: readonly Extractor<any>[],
  context: ExtractorRuntimeContext,
): Promise<Extractor<any>[]> {
  return Promise.all(extractors.map(extractor => extractor.resolve(context)));
}

export function validateExtractorList(extractors: readonly Extractor<any>[]): Extractor<any>[] {
  const seen = new Map<string, string>();
  for (const extractor of extractors) {
    assertValidSlug(extractor.slug, extractor.name);
    if (!extractor.internal && RESERVED_XML_TAGS.has(extractor.slug)) {
      throw new Error(`Extractor slug "${extractor.slug}" is reserved by Observational Memory.`);
    }
    const previous = seen.get(extractor.slug);
    if (previous) {
      throw new Error(`Duplicate extractor slug "${extractor.slug}" from "${previous}" and "${extractor.name}".`);
    }
    seen.set(extractor.slug, extractor.name);
  }
  return [...extractors];
}

function isJsonLike(value: string): boolean {
  return /^(?:[\[{"-]|\d|true\b|false\b|null\b)/.test(value.trim());
}

function candidateValues(raw: string): unknown[] {
  const trimmed = raw.trim();
  const candidates: unknown[] = [];
  const add = (value: unknown) => {
    if (!candidates.some(candidate => Object.is(candidate, value))) {
      candidates.push(value);
    }
  };

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Rename one extractor so each slug is unique.
  2. Deduplicate the extractor array before passing it to the processor (e.g. by slug via slugifyExtractorName).
  3. If merging config sources, prefer user-supplied extractors over defaults with the same slug instead of concatenating both.

Example fix

// before
[new Extractor({ name: 'User Goals', ... }), new Extractor({ name: 'user-goals!', ... })]
// after
[new Extractor({ name: 'User Goals', ... }), new Extractor({ name: 'User Milestones', ... })]
Defensive patterns

Strategy: validation

Validate before calling

function dedupeBySlug(extractors) {
  const seen = new Map();
  return extractors.filter(e => {
    if (seen.has(e.slug)) return false;
    seen.set(e.slug, e.name);
    return true;
  });
}

Type guard

function hasUniqueSlugs(list) {
  return new Set(list.map(e => e.slug)).size === list.length;
}

Try / catch

try {
  validateExtractorList(extractors);
} catch (e) {
  if (e.message.includes('Duplicate extractor slug')) {
    logger.error('Duplicate extractor slugs in list', { msg: e.message });
  } else { throw e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/7d8dff336a81cf50. Report an issue: GitHub.