mastra-ai/mastra · error

Version number ${input.versionNumber} already exists for ski

Error message

Version number ${input.versionNumber} already exists for skill ${input.skillId}

What it means

`createVersion` enforces that the `(skillId, versionNumber)` pair is unique: iterating existing versions, it throws if another version of the same skill already uses `input.versionNumber`. A skill cannot have two records with the same version number.

Source

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

      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);
  }

  async getVersion(id: string): Promise<SkillVersion | null> {
    const version = this.db.skillVersions.get(id);
    return version ? this.deepCopyVersion(version) : null;
  }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Increment `versionNumber` for the skill before creating a new version (query existing versions and take max + 1).
  2. Fetch the skill's versions first and skip creation if the number already exists.
  3. Catch the error and treat as idempotent when the stored version content is identical.
  4. Serialize publishes (lock/queue) so concurrent jobs can't claim the same version number.

Example fix

// before
await storage.createVersion({ id: genId(), skillId: 'my-skill', versionNumber: 1 });
// after
const versions = await storage.listVersions('my-skill', { perPage: false });
const next = Math.max(0, ...versions.versions.map(v => v.versionNumber)) + 1;
await storage.createVersion({ id: genId(), skillId: 'my-skill', versionNumber: next });
Defensive patterns

Strategy: validation

Validate before calling

async function nextVersionNumber(storage, skillId: string) {
  const { versions } = await storage.listVersions(skillId, { perPage: false });
  return Math.max(0, ...versions.map(v => v.versionNumber)) + 1;
}

Try / catch

try {
  await storage.createVersion(input);
} catch (err) {
  if (err instanceof Error && err.message.includes('already exists for skill')) {
    input.versionNumber = await nextVersionNumber(storage, input.skillId);
    return storage.createVersion(input);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling `createVersion({ ..., skillId: 'x', versionNumber: 2 })` when skill `x` already has a version numbered 2, including via the `create`/`update` wrappers that call `createVersion` internally.

Common situations: Re-publishing a skill without bumping its version number, importing/exporting skill definitions across environments where version numbers collide, or parallel CI jobs publishing the same version concurrently.

Related errors


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