{"record":{"id":"48a30e3959c42a44","repo":"can1357/oh-my-pi","slug":"managed-skill-name-skill-md-has-filestat-nl","errorCode":null,"errorMessage":"Managed skill \"${name}\" SKILL.md has ${fileStat.nlink} hard links; refusing to overwrite a file that may be user-authored elsewhere.","messagePattern":"Managed skill \"(.+?)\" SKILL\\.md has (.+?) hard links; refusing to overwrite a file that may be user-authored elsewhere\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/autolearn/managed-skills.ts","lineNumber":134,"sourceCode":" */\nasync function assertManagedRootSafe(): Promise<void> {\n\tconst rootStat = await fs.lstat(getManagedSkillsDir()).catch(err => {\n\t\tif (isEnoent(err)) return null;\n\t\tthrow err;\n\t});\n\tif (rootStat?.isSymbolicLink()) {\n\t\tthrow new Error(\"The managed-skills root is a symlink; refusing to operate outside the managed directory.\");\n\t}\n}\n\nconst UPDATE_FILE_OPEN_FLAGS = fsConstants.O_WRONLY | fsConstants.O_NOFOLLOW;\n\nfunction assertManagedSkillFileSafeForUpdate(name: string, fileStat: Stats): void {\n\tif (!fileStat.isFile()) {\n\t\tthrow new Error(`Managed skill \"${name}\" SKILL.md is not a regular file; refusing to overwrite it.`);\n\t}\n\tif (fileStat.nlink > 1) {\n\t\tthrow new Error(\n\t\t\t`Managed skill \"${name}\" SKILL.md has ${fileStat.nlink} hard links; refusing to overwrite a file that may be user-authored elsewhere.`,\n\t\t);\n\t}\n}\n\nasync function openManagedSkillFileForUpdate(name: string, file: string) {\n\ttry {\n\t\treturn await fs.open(file, UPDATE_FILE_OPEN_FLAGS);\n\t} catch (err) {\n\t\tif ((err as { code?: string }).code === \"ELOOP\") {\n\t\t\tthrow new Error(`Managed skill \"${name}\" SKILL.md is a symlink; refusing to overwrite it.`);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\n/** Create or update a managed `SKILL.md`. Returns the resolved file path. */\nexport async function writeManagedSkill(input: WriteManagedSkillInput): Promise<{ path: string }> {","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/autolearn/managed-skills.ts#L116-L152","documentation":"Managed skills are machine-generated, so overwriting a SKILL.md with more than one hard link could destroy a file the user authored elsewhere (same inode under another path). assertManagedSkillFileSafeForUpdate throws when fileStat.nlink > 1.","triggerScenarios":"writeManagedSkill targets an existing SKILL.md whose inode has multiple hard links — typically because someone hard-linked the file into (or out of) the managed directory.","commonSituations":"A backup or dotfiles tool hard-linked skills into the managed dir; a user ran `ln` to alias a managed skill to their own skills directory; deduplicating filesystems creating links.","solutions":["Replace the hard-linked file with an independent copy (cp --remove-destination or rm then recreate), then retry","Find all links with `find ~/.omp -samefile <path>` and remove the ones you don't want","If you want a user-authored copy, move it out of managed-skills and into ~/.omp/agent/skills as a normal file"],"exampleFix":"// before (shell)\nln ~/.omp/agent/skills/foo/SKILL.md ~/.omp/agent/managed-skills/foo/SKILL.md\n// after (shell)\nrm ~/.omp/agent/managed-skills/foo/SKILL.md && cp ~/.omp/agent/skills/foo/SKILL.md ~/.omp/agent/managed-skills/foo/SKILL.md","handlingStrategy":"validation","validationCode":"import { stat } from \"node:fs/promises\";\nconst st = await stat(skillMdPath).catch(() => null);\nif (st && st.nlink > 1) throw new Error(\"SKILL.md has multiple hard links\");","typeGuard":"function isSingleLinkedFile(st: { isFile(): boolean; nlink: number }): boolean {\n  return st.isFile() && st.nlink === 1;\n}","tryCatchPattern":"try {\n  await writeManagedSkill({ name, action: \"update\", ... });\n} catch (err) {\n  if (String((err as Error).message).includes(\"hard links\")) {\n    // break the link: rm + cp the content back, then retry\n  } else throw err;\n}","preventionTips":["Use `cp`, not `ln`, when copying skills into managed-skills","Find existing links with `find ~/.omp -samefile <path>` before editing","Avoid dedup/backup tools that hard-link files under ~/.omp"],"tags":["filesystem","hardlink","safety"],"backgroundTag":"hardlink-overwrite-guard","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}