mastra-ai/mastra · error

Knowledge already exists: ${record.id}

Error message

Knowledge already exists: ${record.id}

What it means

#appendKnowledge generates a record ID (or takes input.id) and inserts into knowledgeRecords. If a record with that ID already exists, it throws to enforce record-ID uniqueness — duplicate insertion would corrupt the upsert/delete changelog. This only happens when callers supply their own input.id that collides.

Source

Thrown at packages/core/src/storage/domains/knowledge/inmemory.ts:342

  #appendKnowledge(input: AppendKnowledgeInput): KnowledgeRecord {
    const node = nodeReferenceId(input.node);
    const parent = this.#resolveTerminalNode(node);
    if (!parent) throw new KnowledgeNotFoundError('node', node);
    const scope = canonicalizeKnowledgeScope(input.scope);
    assertKnowledgeScopeWithinCeiling(scope, input.maxScope);
    const record: KnowledgeRecord = {
      id: input.id ?? createKnowledgeUlid(),
      node: parent.id,
      text: input.text,
      scope,
      sourceThreadId: input.sourceThreadId,
      capturedAt: new Date(),
      when: input.when ? new Date(input.when) : undefined,
      maxScope: input.maxScope,
      metadata: input.metadata,
    };
    if (this.#db.knowledgeRecords.has(record.id)) throw new Error(`Knowledge already exists: ${record.id}`);
    this.#db.knowledgeRecords.set(record.id, record);
    this.#replaceMentions('record', record.id, record.text, input.resolutionScope, input.defaultScope);
    parent.updatedAt = new Date();
    this.#recordActivity('record-created', 'record', record.id, scope, input.sourceThreadId);
    this.#enqueue('record', record.id, 'upsert', record.id, scope);
    return cloneRecord(record);
  }

  async getKnowledge({
    id,
    includeDeleted = false,
  }: {
    id: string;
    includeDeleted?: boolean;
  }): Promise<KnowledgeRecord | null> {
    const record = this.#db.knowledgeRecords.get(id);
    if (!record || (record.deletedAt && !includeDeleted)) return null;
    return cloneRecord(record);

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Omit input.id and let the storage generate a fresh ULID.
  2. Before appending with a custom ID, check whether the store already holds it (listKnowledge / get by id) and skip or update instead.
  3. Make retry logic idempotent: catch the duplicate and treat the existing record as the outcome.
  4. Namespace custom IDs (source system + original id) to avoid collisions.

Example fix

// before
await storage.appendKnowledge({ id: rec.id, node, text, scope }); // rec.id may already exist
// after
const existing = await storage.getKnowledge(rec.id);
if (!existing) await storage.appendKnowledge({ id: rec.id, node, text, scope });
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await storage.listKnowledge({ /* filters */ });
if (existing.records.some(r => r.id === desiredId)) skip = true;

Try / catch

try {
  await storage.appendKnowledge({ id: desiredId, node, text, scope });
} catch (e) {
  if (e.message.startsWith('Knowledge already exists')) {
    return; // idempotent retry: record already present
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling appendKnowledge() with an explicit input.id equal to an existing record's ID; replaying the same append twice with deterministic IDs (e.g., derived from source content); concurrent appends using the same client-generated ID.

Common situations: Idempotent-retry logic reusing the same ID but not expecting a duplicate error; migration scripts importing records with original IDs into a store that already has some; test fixtures re-initialized without clearing the store.

Related errors


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