mastra-ai/mastra · warning · HTTPException

Skill with id ${id} already exists

Error message

Skill with id ${id} already exists

What it means

This 409 Conflict is thrown after `skillStore.getById(id)` finds an existing skill with the same derived or provided ID, so the create would violate uniqueness. The handler deliberately checks-and-conflicts instead of upserting. Clients must choose a new ID or use the update endpoint.

Source

Thrown at packages/server/src/server/handlers/stored-skills.ts:337

      const skillStore = await storage.getStore('skills');
      if (!skillStore) {
        throw new HTTPException(500, { message: 'Skills storage domain is not available' });
      }

      // Derive ID from name if not explicitly provided
      const id = providedId || toSlug(name);

      if (!id) {
        throw new HTTPException(400, {
          message: 'Could not derive skill ID from name. Please provide an explicit id.',
        });
      }

      // Check if skill with this ID already exists
      const existing = await skillStore.getById(id);
      if (existing) {
        throw new HTTPException(409, { message: `Skill with id ${id} already exists` });
      }

      // Force authorId from the authenticated caller; ignore any body-provided value.
      // No owner = always public (no auth / no user context).
      // With an owner, respect the client's choice, defaulting to 'private'.
      const authorId = getCallerAuthorId(requestContext) ?? undefined;
      const visibility: 'private' | 'public' = authorId ? (bodyVisibility ?? 'private') : 'public';

      // Derive references/scripts/assets path arrays from the files tree
      // so agents can discover them via skill_read even when only `files` is provided.
      const indexedPaths = extractIndexedPathsFromFiles(files, { references, scripts, assets });

      await skillStore.create({
        skill: {
          id,
          authorId,
          visibility,
          name,

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Use the update endpoint (PUT /api/stored-skills/:id) instead of create when the ID already exists
  2. Pick a unique ID or rename the skill so the derived slug differs
  3. Make scripts idempotent: check existence first or treat 409 as success
  4. Add a distinguishing suffix (version, owner, timestamp) to generated IDs

Example fix

// before
await createSkill({ id: 'deploy-helper', name: 'Deploy Helper' }); // 409 on retry
// after
const res = await createSkill({ id: 'deploy-helper', name: 'Deploy Helper' });
if (res.status === 409) await updateSkill('deploy-helper', { description: 'updated' });
Defensive patterns

Strategy: fallback

Validate before calling

const existing = await fetch(`/api/stored-skills/${id}`);
if (existing.ok) throw new Error(`Skill ${id} already exists — use update instead`);

Type guard

null

Try / catch

try {
  await createStoredSkill({ id, name });
} catch (e) {
  if (String((e as Error).message).includes('already exists')) {
    return updateStoredSkill(id, { name }); // fall back to update
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /api/stored-skills with an `id` (or a `name` that slugifies to an ID) that already exists in the skills store; retrying a create that actually succeeded the first time; two users independently creating a skill with the same common name where slugs collide.

Common situations: Re-running a seed/bootstrap script without idempotency; name like 'test' colliding with an existing skill; duplicate submissions from a double-clicked form button.

Related errors


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