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
- List project skills first and use the exact name returned (skill.name)
- Check the skill's scope — user-scope skills need the user-scope deletion path
- Handle 404 idempotently (treat already-deleted as success) in retry logic
- 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
- Look up exact skill.name via listSkills before deleting
- Check skill scope — user-scope skills aren't deletable via this path
- Treat 404 as idempotent success in deletion retries
- Match name casing exactly on case-sensitive filesystems
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
- app_revision_not_found
- connector_mapping_not_found
- connector_sync_event_not_found
- OpenWork server cannot remove skills for this workspace.
- OpenWork server unavailable. Connect to remove skills.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/6ff897bd11f2910b.
Report an issue: GitHub.