mastra-ai/mastra · error

Version with id ${input.id} already exists

Error message

Version with id ${input.id} already exists

What it means

`createVersion` enforces that skill version IDs are unique within the in-memory store. If `this.db.skillVersions` already contains a version with `input.id`, it throws before inserting. This prevents silently overwriting an existing immutable version record.

Source

Thrown at packages/core/src/storage/domains/skills/inmemory.ts:315

    const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);

    return {
      skills: clonedConfigs.slice(offset, offset + perPage),
      total: clonedConfigs.length,
      page,
      perPage: perPageForResponse,
      hasMore: offset + perPage < clonedConfigs.length,
    };
  }

  // ==========================================================================
  // Skill Version Methods
  // ==========================================================================

  async createVersion(input: CreateSkillVersionInput): Promise<SkillVersion> {
    // Check if version with this ID already exists
    if (this.db.skillVersions.has(input.id)) {
      throw new Error(`Version with id ${input.id} already exists`);
    }

    // Check for duplicate (skillId, versionNumber) pair
    for (const version of this.db.skillVersions.values()) {
      if (version.skillId === input.skillId && version.versionNumber === input.versionNumber) {
        throw new Error(`Version number ${input.versionNumber} already exists for skill ${input.skillId}`);
      }
    }

    const version: SkillVersion = {
      ...input,
      createdAt: new Date(),
    };

    // Deep clone before storing
    this.db.skillVersions.set(input.id, this.deepCopyVersion(version));
    return this.deepCopyVersion(version);
  }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Generate a unique version ID (UUID/nanoid) instead of a deterministic reused one.
  2. Before inserting, check existence with `getVersion(input.id)` and update or skip instead of creating.
  3. Wrap the call in try/catch and treat the duplicate-ID error as idempotent success if the existing record matches.
  4. For seed scripts, clear skillVersions or use upsert semantics before re-seeding.

Example fix

// before
await storage.createVersion({ id: 'my-skill-1', skillId: 'my-skill', versionNumber: 1 });
// after
const existing = await storage.getVersion('my-skill-1');
if (!existing) await storage.createVersion({ id: crypto.randomUUID(), skillId: 'my-skill', versionNumber: 1 });
Defensive patterns

Strategy: fallback

Validate before calling

async function createVersionIfAbsent(storage, input) {
  const existing = await storage.getVersion(input.id);
  if (existing) return existing;
  return storage.createVersion(input);
}

Try / catch

try {
  await storage.createVersion(input);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Version with id')) {
    return storage.getVersion(input.id); // idempotent: return existing
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling `createVersion({ id: '<already-used-id>', ... })`, or calling the public `create`/`update` methods that internally invoke `createVersion` with an ID that already exists in storage.

Common situations: Re-running seed/fixture scripts that generate deterministic IDs (e.g. `skill-v1`) without clearing the store, retrying a failed request after the first attempt actually succeeded, or concurrent writers racing to insert the same version ID.

Related errors


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