{"record":{"id":"fc8ea297925618f0","repo":"can1357/oh-my-pi","slug":"managed-skill-name-already-exists-use-action","errorCode":null,"errorMessage":"Managed skill \"${name}\" already exists. Use action \"update\" to change it.","messagePattern":"Managed skill \"(.+?)\" already exists\\. Use action \"update\" to change it\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/autolearn/managed-skills.ts","lineNumber":198,"sourceCode":"\t\t// component, so a symlinked `dir` is caught here.\n\t\tconst dirStat = await fs.lstat(dir).catch(err => {\n\t\t\tif (isEnoent(err)) return null;\n\t\t\tthrow err;\n\t\t});\n\t\tif (dirStat?.isSymbolicLink()) {\n\t\t\tthrow new Error(\n\t\t\t\t`Managed skill \"${name}\" resolves through a symlink; refusing to write outside the managed directory.`,\n\t\t\t);\n\t\t}\n\t\tif (input.action === \"create\") {\n\t\t\tawait fs.mkdir(dir, { recursive: true });\n\t\t\t// O_CREAT|O_EXCL (\"wx\"): atomic create that fails if the file already\n\t\t\t// exists (closing the check-then-write race) and refuses a symlinked SKILL.md.\n\t\t\ttry {\n\t\t\t\tawait fs.writeFile(file, content, { flag: \"wx\" });\n\t\t\t} catch (err) {\n\t\t\t\tif ((err as { code?: string }).code === \"EEXIST\") {\n\t\t\t\t\tthrow new Error(`Managed skill \"${name}\" already exists. Use action \"update\" to change it.`);\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\treturn { path: file };\n\t\t}\n\t\t// update: the file must already exist, be a plain managed file, and must\n\t\t// not share an inode with a user-authored file via hard link. Open the\n\t\t// checked file handle before truncating so a path swap after lstat cannot\n\t\t// redirect the write into a symlink or newly hard-linked target.\n\t\tconst fileStat = await fs.lstat(file).catch(err => {\n\t\t\tif (isEnoent(err)) return null;\n\t\t\tthrow err;\n\t\t});\n\t\tif (fileStat === null) {\n\t\t\tthrow new Error(`Managed skill \"${name}\" does not exist. Use action \"create\" to add it.`);\n\t\t}\n\t\tif (fileStat.isSymbolicLink()) {\n\t\t\tthrow new Error(`Managed skill \"${name}\" SKILL.md is a symlink; refusing to overwrite it.`);","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/autolearn/managed-skills.ts#L180-L216","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait writeManagedSkill({ action: \"create\", name: \"my-skill\", description: \"d\", body: \"b\" });\n// after\nconst file = `${getManagedSkillsDir()}/my-skill/SKILL.md`;\nconst exists = await Bun.file(file).exists();\nawait writeManagedSkill({ action: exists ? \"update\" : \"create\", name: \"my-skill\", description: \"d\", body: \"b\" });","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs/promises\";\nconst file = `${getManagedSkillsDir()}/${sanitizeSkillName(name)}/SKILL.md`;\nconst action = await fs.lstat(file).then(s => s?.isFile() ? \"update\" : \"create\").catch(() => \"create\");","typeGuard":null,"tryCatchPattern":"try {\n  await writeManagedSkill({ action: \"create\", name, description, body });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('already exists')) {\n    await writeManagedSkill({ action: \"update\", name, description, body });\n  } else throw err;\n}","preventionTips":["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."],"tags":["filesystem","duplicate-resource","conflict"],"backgroundTag":"file-already-exists-conflict","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}