{"record":{"id":"ab26d7a99794d68a","repo":"mastra-ai/mastra","slug":"skill-with-id-id-already-exists-ab26d7","errorCode":null,"errorMessage":"Skill with id ${id} already exists","messagePattern":"Skill with id (.+?) already exists","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"packages/server/src/server/handlers/stored-skills.ts","lineNumber":337,"sourceCode":"\n      const skillStore = await storage.getStore('skills');\n      if (!skillStore) {\n        throw new HTTPException(500, { message: 'Skills storage domain is not available' });\n      }\n\n      // Derive ID from name if not explicitly provided\n      const id = providedId || toSlug(name);\n\n      if (!id) {\n        throw new HTTPException(400, {\n          message: 'Could not derive skill ID from name. Please provide an explicit id.',\n        });\n      }\n\n      // Check if skill with this ID already exists\n      const existing = await skillStore.getById(id);\n      if (existing) {\n        throw new HTTPException(409, { message: `Skill with id ${id} already exists` });\n      }\n\n      // Force authorId from the authenticated caller; ignore any body-provided value.\n      // No owner = always public (no auth / no user context).\n      // With an owner, respect the client's choice, defaulting to 'private'.\n      const authorId = getCallerAuthorId(requestContext) ?? undefined;\n      const visibility: 'private' | 'public' = authorId ? (bodyVisibility ?? 'private') : 'public';\n\n      // Derive references/scripts/assets path arrays from the files tree\n      // so agents can discover them via skill_read even when only `files` is provided.\n      const indexedPaths = extractIndexedPathsFromFiles(files, { references, scripts, assets });\n\n      await skillStore.create({\n        skill: {\n          id,\n          authorId,\n          visibility,\n          name,","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/stored-skills.ts#L319-L355","documentation":"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.","triggerScenarios":"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.","commonSituations":"Re-running a seed/bootstrap script without idempotency; name like 'test' colliding with an existing skill; duplicate submissions from a double-clicked form button.","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"],"exampleFix":"// before\nawait createSkill({ id: 'deploy-helper', name: 'Deploy Helper' }); // 409 on retry\n// after\nconst res = await createSkill({ id: 'deploy-helper', name: 'Deploy Helper' });\nif (res.status === 409) await updateSkill('deploy-helper', { description: 'updated' });","handlingStrategy":"fallback","validationCode":"const existing = await fetch(`/api/stored-skills/${id}`);\nif (existing.ok) throw new Error(`Skill ${id} already exists — use update instead`);","typeGuard":"null","tryCatchPattern":"try {\n  await createStoredSkill({ id, name });\n} catch (e) {\n  if (String((e as Error).message).includes('already exists')) {\n    return updateStoredSkill(id, { name }); // fall back to update\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["conflict","http-409","duplicate","rest-api"],"backgroundTag":"duplicate-resource-conflict","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}