{"record":{"id":"62f2f8ab85dda2c2","repo":"mastra-ai/mastra","slug":"skill-with-id-id-already-exists","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/builder-registry.ts","lineNumber":374,"sourceCode":"      // body as the agent-facing `instructions` instead of polluting it\n      // with raw YAML metadata. SKILL.md missing or unparseable simply\n      // yields a null snapshot — registry-provided values then fill in.\n      const snapshot = parseSkillSnapshot(result.files);\n\n      const resolvedName = snapshot?.name ?? safeSkillId;\n      const description = snapshot?.description ?? `Imported from ${owner}/${repo}`;\n      const id = toSlug(resolvedName) || safeSkillId;\n\n      if (!id) {\n        throw new HTTPException(400, {\n          message: 'Could not derive skill ID from registry skill metadata.',\n        });\n      }\n\n      // Reject collisions instead of silently overwriting; UI offers \"Open existing\".\n      const existing = await skillStore.getById(id);\n      if (existing) {\n        throw new HTTPException(409, {\n          message: `Skill with id \"${id}\" already exists.`,\n          // Surface the existing id so the client can deep-link.\n          cause: { storedSkillId: id },\n        });\n      }\n\n      // Match the standard create flow: no caller = always public, otherwise default private.\n      const authorId = getCallerAuthorId(requestContext) ?? undefined;\n      const visibility: 'private' | 'public' = authorId ? (bodyVisibility ?? 'private') : 'public';\n\n      // Use the SKILL.md body (post-frontmatter) as instructions. Frontmatter\n      // values are already lifted into structured columns above, so re-storing\n      // them in `instructions` would both duplicate metadata and feed YAML\n      // into the agent's prompt. Fall back to description when no usable body\n      // exists so `resolved.snapshot.instructions` stays non-empty.\n      const instructions = snapshot?.instructions?.trim() ? snapshot.instructions : description;\n\n      await skillStore.create({","sourceCodeStart":356,"sourceCodeEnd":392,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/builder-registry.ts#L356-L392","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait installSkill({ registryId, owner, repo, skillName: 'review' }); // 409: exists\n\n// after\ntry {\n  await installSkill({ registryId, owner, repo, skillName: 'review' });\n} catch (e) {\n  if (e.status === 409 && e.cause?.storedSkillId) {\n    openExistingSkill(e.cause.storedSkillId); // reuse instead of reinstall\n  }\n}","handlingStrategy":"try-catch","validationCode":"const slug = toSlug(skillName);\nconst existing = await getStoredSkill(slug);\nif (existing) {\n  // skip install or prompt user to open/replace\n  openExistingSkill(slug);\n} else {\n  await installSkill({ registryId, owner, repo, skillName });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await installSkill(payload);\n} catch (e) {\n  if (e.status === 409 && e.cause?.storedSkillId) {\n    openExistingSkill(e.cause.storedSkillId); // deep-link offered by the API\n  } else throw e;\n}","preventionTips":["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."],"tags":["http-409","builder-registry","skills","conflict"],"backgroundTag":"duplicate-resource-conflict","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}