different-ai/openwork · error · ApiError

skill_not_found

skill_not_found

Error message

Skill not found: ${trimmed}

What it means

This 404 skill_not_found error is thrown by deleteSkill when no project-scope skill matching the requested name exists in the workspace's skills directory. Deletion resolves the skill via listSkills and requires an exact name match with scope "project"; nested layouts (skills/<domain>/<name>/SKILL.md) are searched too, but user/global-scope skills will not match.

Source

Thrown at apps/server/src/skills.ts:291

  return { path: skillPath, action: existed ? "updated" : "added" };
}

export async function deleteSkill(workspaceRoot: string, name: string): Promise<{ path: string }> {
  const trimmed = name.trim();
  validateSkillName(trimmed);
  const baseDir = projectSkillsDir(workspaceRoot);
  const flatDir = join(baseDir, trimmed);
  if (await exists(join(flatDir, "SKILL.md"))) {
    await rm(flatDir, { recursive: true, force: true });
    return { path: flatDir };
  }
  // Nested layout: skills/<domain>/<name>/SKILL.md (e.g. skills installed by
  // marketplace plugin bundles are namespaced under a plugin folder). Listing
  // supports this layout, so deletion must resolve it the same way.
  const items = await listSkills(workspaceRoot, false);
  const item = items.find((skill) => skill.name === trimmed && skill.scope === "project");
  if (!item) {
    throw new ApiError(404, "skill_not_found", `Skill not found: ${trimmed}`);
  }
  const skillDir = dirname(item.path);
  await rm(skillDir, { recursive: true, force: true });
  return { path: skillDir };
}

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. List project skills first and use the exact name returned (skill.name)
  2. Check the skill's scope — user-scope skills need the user-scope deletion path
  3. Handle 404 idempotently (treat already-deleted as success) in retry logic
  4. Verify filename casing matches the requested name

Example fix

// before
await deleteSkill("My-Skill"); // 404: actual name is "my-skill"
// after
const skills = await listSkills(root);
const target = skills.find(s => s.scope === "project" && s.name === "my-skill");
if (target) await deleteSkill(target.name);
Defensive patterns

Strategy: try-catch

Validate before calling

const skills = await listSkills(root);
const exists = skills.some(s => s.scope === "project" && s.name === name);
if (!exists) throw new Error(`skill "${name}" not found in project scope`);

Try / catch

try {
  await deleteSkill(name);
} catch (e) {
  if (e.code === "skill_not_found") return { deleted: false }; // idempotent
  throw e;
}

Prevention

When it happens

Trigger: DELETEing a skill whose name doesn't match any installed project skill — wrong name, wrong casing, the skill is user-scope instead of project-scope, or it was already deleted.

Common situations: Typos in skill names; case-sensitivity mismatch (Linux filesystem); attempting to delete a user-level skill via the project endpoint; a previous delete already removed it and the client retried.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/6ff897bd11f2910b. Report an issue: GitHub.