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
- Use the update endpoint (PUT /api/stored-skills/:id) instead of create when the ID already exists
- Pick a unique ID or rename the skill so the derived slug differs
- Make scripts idempotent: check existence first or treat 409 as success
- 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
- Prefix generated IDs with a namespace/owner to reduce slug collisions
- Make seed scripts idempotent (check-then-create or upsert semantics)
- Debounce/duplicate-guard form submissions in the UI
- Treat 409 as 'already done' in retried batch jobs
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
- Scorer definition with id ${id} already exists
- Agent with id ${id} already exists
- MCP client with id ${id} already exists
- Workspace with id ${id} already exists
- Skipped: Scorer ${filename} already exists at ${scorersPath}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/ab26d7a99794d68a.
Report an issue: GitHub.