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
- Check if the skill already exists (the 409's cause.storedSkillId gives the ID) and open/reuse it instead of installing.
- Delete the existing skill first if you truly want to replace it, then reinstall.
- Use the UI's 'Open existing' deep-link offered on collision.
- 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
- Check for an existing skill by slug before installing.
- Make install actions idempotent in UI (disable button while installed).
- Handle 409 + cause.storedSkillId as 'already installed', not an error.
- If replacing is intended, delete the existing skill first, then install.
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
- Could not find skill "${skillName}" in ${owner}/${repo}.
- Could not derive skill ID from registry skill metadata.
- DATASET_ITEM_IDENTITY_CONFLICT
- Agent with id ${id} already exists
- MCP client with id ${id} already exists
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/62f2f8ab85dda2c2.
Report an issue: GitHub.