mastra-ai/mastra · warning · HTTPException

Skill with id "${id}" already exists.

Error message

Skill with id "${id}" already exists.

What it means

Before persisting an imported skill, the handler checks skillStore.getById(id) and throws a 409 HTTPException when a skill with the derived ID already exists. Collisions are rejected instead of silently overwriting; the cause carries storedSkillId so clients can deep-link to the existing skill (the UI surfaces an 'Open existing' action).

Source

Thrown at packages/server/src/server/handlers/builder-registry.ts:374

      // body as the agent-facing `instructions` instead of polluting it
      // with raw YAML metadata. SKILL.md missing or unparseable simply
      // yields a null snapshot — registry-provided values then fill in.
      const snapshot = parseSkillSnapshot(result.files);

      const resolvedName = snapshot?.name ?? safeSkillId;
      const description = snapshot?.description ?? `Imported from ${owner}/${repo}`;
      const id = toSlug(resolvedName) || safeSkillId;

      if (!id) {
        throw new HTTPException(400, {
          message: 'Could not derive skill ID from registry skill metadata.',
        });
      }

      // Reject collisions instead of silently overwriting; UI offers "Open existing".
      const existing = await skillStore.getById(id);
      if (existing) {
        throw new HTTPException(409, {
          message: `Skill with id "${id}" already exists.`,
          // Surface the existing id so the client can deep-link.
          cause: { storedSkillId: id },
        });
      }

      // Match the standard create flow: no caller = always public, otherwise default private.
      const authorId = getCallerAuthorId(requestContext) ?? undefined;
      const visibility: 'private' | 'public' = authorId ? (bodyVisibility ?? 'private') : 'public';

      // Use the SKILL.md body (post-frontmatter) as instructions. Frontmatter
      // values are already lifted into structured columns above, so re-storing
      // them in `instructions` would both duplicate metadata and feed YAML
      // into the agent's prompt. Fall back to description when no usable body
      // exists so `resolved.snapshot.instructions` stays non-empty.
      const instructions = snapshot?.instructions?.trim() ? snapshot.instructions : description;

      await skillStore.create({

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Check if the skill already exists (the 409's cause.storedSkillId gives the ID) and open/reuse it instead of installing.
  2. Delete the existing skill first if you truly want to replace it, then reinstall.
  3. Use the UI's 'Open existing' deep-link offered on collision.
  4. If the collision is a false positive from slug normalization, rename the upstream skill so IDs differ.

Example fix

// before
await installSkill({ registryId, owner, repo, skillName: 'review' }); // 409: exists

// after
try {
  await installSkill({ registryId, owner, repo, skillName: 'review' });
} catch (e) {
  if (e.status === 409 && e.cause?.storedSkillId) {
    openExistingSkill(e.cause.storedSkillId); // reuse instead of reinstall
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

const slug = toSlug(skillName);
const existing = await getStoredSkill(slug);
if (existing) {
  // skip install or prompt user to open/replace
  openExistingSkill(slug);
} else {
  await installSkill({ registryId, owner, repo, skillName });
}

Try / catch

try {
  await installSkill(payload);
} catch (e) {
  if (e.status === 409 && e.cause?.storedSkillId) {
    openExistingSkill(e.cause.storedSkillId); // deep-link offered by the API
  } else throw e;
}

Prevention

When it happens

Trigger: POSTing the install route for a skill whose slugified ID matches an already-stored skill — reinstalling the same skill, installing a differently-named upstream skill that slugs to the same ID, or an earlier partial install that left a record behind.

Common situations: Clicking install twice on the same skill; two upstream skills whose names normalize to the same slug; a previous failed install that persisted before erroring; team members independently importing the same skill.

Related errors


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