can1357/oh-my-pi · error
Managed skill "${name}" already exists. Use action "update"
Error message
Managed skill "${name}" already exists. Use action "update" to change it. What it means
writeManagedSkill with action "create" writes SKILL.md atomically using O_CREAT|O_EXCL ("wx"), which fails with EEXIST if the file already exists. The library converts that EEXIST into this error instead of silently overwriting an existing managed skill. It enforces the create/update contract: create only makes new skills, update mutates existing ones.
Source
Thrown at packages/coding-agent/src/autolearn/managed-skills.ts:198
// component, so a symlinked `dir` is caught here.
const dirStat = await fs.lstat(dir).catch(err => {
if (isEnoent(err)) return null;
throw err;
});
if (dirStat?.isSymbolicLink()) {
throw new Error(
`Managed skill "${name}" resolves through a symlink; refusing to write outside the managed directory.`,
);
}
if (input.action === "create") {
await fs.mkdir(dir, { recursive: true });
// O_CREAT|O_EXCL ("wx"): atomic create that fails if the file already
// exists (closing the check-then-write race) and refuses a symlinked SKILL.md.
try {
await fs.writeFile(file, content, { flag: "wx" });
} catch (err) {
if ((err as { code?: string }).code === "EEXIST") {
throw new Error(`Managed skill "${name}" already exists. Use action "update" to change it.`);
}
throw err;
}
return { path: file };
}
// update: the file must already exist, be a plain managed file, and must
// not share an inode with a user-authored file via hard link. Open the
// checked file handle before truncating so a path swap after lstat cannot
// redirect the write into a symlink or newly hard-linked target.
const fileStat = await fs.lstat(file).catch(err => {
if (isEnoent(err)) return null;
throw err;
});
if (fileStat === null) {
throw new Error(`Managed skill "${name}" does not exist. Use action "create" to add it.`);
}
if (fileStat.isSymbolicLink()) {
throw new Error(`Managed skill "${name}" SKILL.md is a symlink; refusing to overwrite it.`);View on GitHub (pinned to 9690622007)
Solutions
- Call writeManagedSkill with action "update" instead of "create" to overwrite the existing skill.
- Check existence first (lstat the managed SKILL.md) and pick create/update accordingly.
- Delete the skill with deleteManagedSkill(name) if you truly want a fresh create.
Example fix
// before
await writeManagedSkill({ action: "create", name: "my-skill", description: "d", body: "b" });
// after
const file = `${getManagedSkillsDir()}/my-skill/SKILL.md`;
const exists = await Bun.file(file).exists();
await writeManagedSkill({ action: exists ? "update" : "create", name: "my-skill", description: "d", body: "b" }); Defensive patterns
Strategy: validation
Validate before calling
import * as fs from "node:fs/promises";
const file = `${getManagedSkillsDir()}/${sanitizeSkillName(name)}/SKILL.md`;
const action = await fs.lstat(file).then(s => s?.isFile() ? "update" : "create").catch(() => "create"); Try / catch
try {
await writeManagedSkill({ action: "create", name, description, body });
} catch (err) {
if (err instanceof Error && err.message.includes('already exists')) {
await writeManagedSkill({ action: "update", name, description, body });
} else throw err;
} Prevention
- Always derive create vs update from an lstat of the managed SKILL.md, never from cached state.
- Reuse one helper wrapper around writeManagedSkill that encodes the upsert logic.
- Remember same-name mutations are serialized in-process; don't issue duplicate creates in parallel batches.
When it happens
Trigger: Calling writeManagedSkill({ action: "create", name, ... }) when ~/.omp/agent/managed-skills/<name>/SKILL.md already exists — e.g. the skill was created in a previous session or by a prior tool call.
Common situations: An agent auto-learn flow re-running a create step for a skill it already generated; re-running a batch script without checking existence first; a stale local cache thinking the skill is missing.
Related errors
- Agent file already exists: ${shortenPath(filePath)}
- unknown filetype: {ft_debug}
- Is a directory
- Too many levels of symbolic links
- {}: {error}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/fc8ea297925618f0.
Report an issue: GitHub.