{"record":{"id":"20ce47a80a97de47","repo":"mastra-ai/mastra","slug":"skill-skillname-not-found-at-skillpath","errorCode":null,"errorMessage":"Skill \"${skillName}\" not found at ${skillPath}","messagePattern":"Skill \"(.+?)\" not found at (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"packages/server/src/server/handlers/workspace.ts","lineNumber":1567,"sourceCode":"      if (workspace.filesystem.readOnly) {\n        throw new HTTPException(403, { message: 'Workspace is read-only' });\n      }\n\n      // Validate skill name to prevent path traversal\n      const safeSkillName = assertSafeSkillName(skillName);\n\n      // Look up the skill's actual path from the cache (supports glob-discovered skills).\n      // Only use the discovered path if it's under the skills.sh directory to avoid\n      // accidentally deleting a locally-authored skill with the same name.\n      const allSkills = await workspace.skills?.list();\n      const matchingSkill = allSkills?.find(s => s.name === safeSkillName && s.path.includes(SKILLS_SH_PATH_PREFIX));\n      const skillPath = matchingSkill?.path ?? buildSkillInstallPath(workspace.filesystem, safeSkillName);\n\n      // Check if skill exists on filesystem\n      try {\n        await workspace.filesystem.stat(skillPath);\n      } catch {\n        throw new HTTPException(404, { message: `Skill \"${skillName}\" not found at ${skillPath}` });\n      }\n\n      // Delete the skill directory\n      await workspace.filesystem.rmdir(skillPath, { recursive: true });\n\n      // Surgically remove the skill from the cache\n      if (workspace.skills?.removeSkill) {\n        try {\n          await workspace.skills.removeSkill(skillPath);\n        } catch (cacheError) {\n          console.warn(\n            `[skills-sh] Failed to update cache after remove: ${cacheError instanceof Error ? cacheError.message : String(cacheError)}`,\n          );\n        }\n      }\n\n      return {\n        success: true,","sourceCodeStart":1549,"sourceCodeEnd":1585,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/workspace.ts#L1549-L1585","documentation":"HTTP 404 thrown when the skill directory cannot be `stat`-ed at the computed path. The handler first looks up the skill in the skills cache (only if its path is under the skills.sh prefix), otherwise falls back to buildSkillInstallPath; if neither location exists on the filesystem, it reports the skill as not found and includes the exact path checked.","triggerScenarios":"POST /workspaces/:workspaceId/skills-sh/remove with a skillName that was never installed, was already removed, has a name differing in case/spelling from the installed directory, or was installed outside the skills.sh prefix and at a non-default path.","commonSituations":"Retrying a remove after a successful delete (double-click, replayed request); typos in skill name; skill installed manually under a custom directory so cache discovery misses it; workspace filesystem reset between install and remove.","solutions":["List the workspace skills first and confirm the exact skill name/path before removing.","Correct the skillName spelling/casing to match the installed directory.","If the skill is stale state (already deleted), treat HTTP 404 as idempotent success in your caller.","If installed at a custom path, remove it via the generic filesystem API instead of the skills-sh remove endpoint."],"exampleFix":"// before\nawait ws.skillsSh.remove({ skillName: 'PdfTools' }); // 404\n\n// after\nconst skills = await ws.skills.list();\nconst target = skills.find(s => s.name.toLowerCase() === 'pdftools');\nif (target) await ws.skillsSh.remove({ skillName: target.name });","handlingStrategy":"validation","validationCode":"const skills = (await workspace.skills?.list()) ?? [];\nconst target = skills.find(s => s.name === skillName);\nif (!target) throw new Error(`Skill \"${skillName}\" is not installed in workspace ${workspaceId}`);","typeGuard":"function isInstalledSkill(skills: Array<{ name: string; path: string }>, name: string) {\n  return skills.find(s => s.name === name) ?? null;\n}","tryCatchPattern":"try {\n  await removeSkill(workspaceId, skillName);\n} catch (e) {\n  if (isHttpException(e, 404) && e.message.includes('not found at')) {\n    return { removed: false, reason: 'already-absent' }; // treat as idempotent success\n  }\n  throw e;\n}","preventionTips":["Always list installed skills and match exact names before removing.","Treat 404 from remove as idempotent success to make retries safe.","Don't hand-write skill paths; use the discovered path from skills.list()."],"tags":["workspace","skills","http-404","not-found"],"backgroundTag":"skill-not-found","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}