{"record":{"id":"e6b9486cd6f59066","repo":"EveryInc/compound-engineering-plugin","slug":"skipping-targetdir-existing-user-managed-symli-e6b948","errorCode":null,"errorMessage":"Skipping ${targetDir}: existing user-managed symlink (not overwritten)","messagePattern":"Skipping (.+?): existing user-managed symlink \\(not overwritten\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/targets/pi.ts","lineNumber":462,"sourceCode":"    if (!isSafeManagedPath(agentsDir, agentFile)) continue\n    const targetPath = path.join(agentsDir, agentFile)\n    if (await isPreservedSymlink(targetPath)) continue\n    await fs.rm(targetPath, { force: true })\n  }\n}\n\n// Returns true when the existing path was preserved (skip cleanup AND the\n// subsequent copy/write -- writing through a preserved symlink would clobber\n// the user's fork, which is worse than not cleaning up at all).\nasync function cleanupCurrentManagedSkillDir(\n  targetDir: string,\n  manifest: PiInstallManifest | null,\n  skillName: string,\n): Promise<boolean> {\n  const stat = await lstatOrNull(targetDir)\n  if (!stat) return false\n  if (stat.isSymbolicLink()) {\n    console.warn(`Skipping ${targetDir}: existing user-managed symlink (not overwritten)`)\n    return true\n  }\n  if (!manifest?.skills.includes(skillName)) {\n    console.warn(`Skipping ${targetDir}: existing unmanaged directory (not overwritten)`)\n    return true\n  }\n  await fs.rm(targetDir, { recursive: true, force: true })\n  return false\n}\n\nasync function cleanupCurrentManagedAgentFile(\n  targetPath: string,\n  manifest: PiInstallManifest | null,\n  agentFileName: string,\n): Promise<boolean> {\n  const stat = await lstatOrNull(targetPath)\n  if (!stat) return false\n  if (stat.isSymbolicLink()) {","sourceCodeStart":444,"sourceCodeEnd":480,"githubUrl":"https://github.com/EveryInc/compound-engineering-plugin/blob/c9c10f8c75412c7232cb2bd663e5fd1cea98d84e/src/targets/pi.ts#L444-L480","documentation":"cleanupCurrentManagedSkillDir in src/targets/pi.ts is the preserve-check for Pi managed skill directories. If the target directory exists as a symlink, it is treated as user-managed and skipped (returns true) — cleanup and install will never follow or overwrite it. This mirrors the shared symlink-preservation policy across target writers.","triggerScenarios":"Running a Pi bundle write/cleanup when a skill directory under the managed skills root exists and lstat reports isSymbolicLink(); reached via the `preserved` path of cleanup.","commonSituations":"Dotfiles setups symlinking individual skill directories (stow/chezmoi); users linking a skill dir to a shared checkout or a customized copy; previous manual replacement of an installed skill with a link.","solutions":["If the symlink is intentional, nothing to do — the directory is preserved.","Remove the symlink (rm the link, not its target) and re-run the install to get the managed skill directory written.","If the link target has customizations, reconcile them into your own copy or the plugin source before removing the link.","Confirm with `ls -la <managed skills root>` which skill entries are symlinks before re-running."],"exampleFix":"// before\nls -la ~/.config/pi/skills/ce-plan  # -> ~/dotfiles/pi-skills/ce-plan\n// after\nrm ~/.config/pi/skills/ce-plan\nbun run convert --to pi","handlingStrategy":"validation","validationCode":"import { lstat } from 'node:fs/promises'\nasync function skillDirIsSymlink(root: string, skillName: string): Promise<boolean> {\n  try { return (await lstat(path.join(root, skillName))).isSymbolicLink() } catch { return false }\n}\n// before install: if (await skillDirIsSymlink(skillsRoot, name)) remove or accept the skip","typeGuard":"function statIsSymlink(stat: { isSymbolicLink(): boolean } | null): boolean {\n  return stat?.isSymbolicLink() === true\n}","tryCatchPattern":null,"preventionTips":["Keep the managed skills root free of hand-created symlinks; place personal skills elsewhere","List the skills root with `ls -la` before install to spot links","Customize skills by editing plugin source/config, not by swapping directories for links"],"tags":["symlink","pi","skills","filesystem-safety"],"backgroundTag":"user-managed-symlink-skipped","analyzedSha":"c9c10f8c75412c7232cb2bd663e5fd1cea98d84e","analyzedAt":"2026-08-31T15:18:07.959Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}