mastra-ai/mastra · error · HTTPException

Skill " " not found at

Error message

Skill "${skillName}" not found at ${skillPath}

What it means

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.

Solutions

  1. List the workspace skills first and confirm the exact skill name/path before removing.
  2. Correct the skillName spelling/casing to match the installed directory.
  3. If the skill is stale state (already deleted), treat HTTP 404 as idempotent success in your caller.
  4. If installed at a custom path, remove it via the generic filesystem API instead of the skills-sh remove endpoint.

Example fix

// before
await ws.skillsSh.remove({ skillName: 'PdfTools' }); // 404

// after
const skills = await ws.skills.list();
const target = skills.find(s => s.name.toLowerCase() === 'pdftools');
if (target) await ws.skillsSh.remove({ skillName: target.name });
Defensive patterns

Strategy: validation

Validate before calling

const skills = (await workspace.skills?.list()) ?? [];
const target = skills.find(s => s.name === skillName);
if (!target) throw new Error(`Skill "${skillName}" is not installed in workspace ${workspaceId}`);

Type guard

function isInstalledSkill(skills: Array<{ name: string; path: string }>, name: string) {
  return skills.find(s => s.name === name) ?? null;
}

Try / catch

try {
  await removeSkill(workspaceId, skillName);
} catch (e) {
  if (isHttpException(e, 404) && e.message.includes('not found at')) {
    return { removed: false, reason: 'already-absent' }; // treat as idempotent success
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/20ce47a80a97de47. Report an issue: GitHub.

Appendix: source

Thrown at packages/server/src/server/handlers/workspace.ts:1567

      if (workspace.filesystem.readOnly) {
        throw new HTTPException(403, { message: 'Workspace is read-only' });
      }

      // Validate skill name to prevent path traversal
      const safeSkillName = assertSafeSkillName(skillName);

      // Look up the skill's actual path from the cache (supports glob-discovered skills).
      // Only use the discovered path if it's under the skills.sh directory to avoid
      // accidentally deleting a locally-authored skill with the same name.
      const allSkills = await workspace.skills?.list();
      const matchingSkill = allSkills?.find(s => s.name === safeSkillName && s.path.includes(SKILLS_SH_PATH_PREFIX));
      const skillPath = matchingSkill?.path ?? buildSkillInstallPath(workspace.filesystem, safeSkillName);

      // Check if skill exists on filesystem
      try {
        await workspace.filesystem.stat(skillPath);
      } catch {
        throw new HTTPException(404, { message: `Skill "${skillName}" not found at ${skillPath}` });
      }

      // Delete the skill directory
      await workspace.filesystem.rmdir(skillPath, { recursive: true });

      // Surgically remove the skill from the cache
      if (workspace.skills?.removeSkill) {
        try {
          await workspace.skills.removeSkill(skillPath);
        } catch (cacheError) {
          console.warn(
            `[skills-sh] Failed to update cache after remove: ${cacheError instanceof Error ? cacheError.message : String(cacheError)}`,
          );
        }
      }

      return {
        success: true,

View on GitHub (pinned to 75dd419e61)