{"record":{"id":"66d1dcea86c52594","repo":"EveryInc/compound-engineering-plugin","slug":"skipping-targetdir-existing-unmanaged-director-66d1dc","errorCode":null,"errorMessage":"Skipping ${targetDir}: existing unmanaged directory (not overwritten)","messagePattern":"Skipping (.+?): existing unmanaged directory \\(not overwritten\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/targets/pi.ts","lineNumber":466,"sourceCode":"  }\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()) {\n    console.warn(`Skipping ${targetPath}: existing user-managed symlink (not overwritten)`)\n    return true\n  }\n  if (!manifest?.agents.includes(agentFileName)) {","sourceCodeStart":448,"sourceCodeEnd":484,"githubUrl":"https://github.com/EveryInc/compound-engineering-plugin/blob/c9c10f8c75412c7232cb2bd663e5fd1cea98d84e/src/targets/pi.ts#L448-L484","documentation":"Also from cleanupCurrentManagedSkillDir in src/targets/pi.ts: when the target skill directory exists as a real directory but is not listed in manifest.skills, it is considered user-created/unmanaged and skipped with this warning. Only directories the manifest proves the plugin installed may be removed (via fs.rm recursive) — everything else is preserved.","triggerScenarios":"Running a Pi install/cleanup where a directory exists at the managed skill location but the loaded Pi install manifest's skills array does not include skillName — hand-created skill dirs, lost/legacy manifest, or names changed between plugin versions.","commonSituations":"Users adding their own skills into the plugin-managed skills root; the manifest deleted or reset (or migrated from legacy, see error 96) so prior installs are no longer recorded; a renamed skill leaving the old directory behind as unmanaged.","solutions":["If the directory is yours, move it out of the plugin-managed skills root to a user skill location so it is not shadowing managed names.","If you want the plugin version, back up any customizations and delete the unmanaged directory, then re-run the install.","If the manifest is stale (this dir WAS plugin-installed previously), reinstall so the manifest regenerates and records it; if truly stale, remove it manually after backup.","Check the manifest's skills array to confirm whether the name is recorded before deleting anything."],"exampleFix":"// before: hand-made skill dir not in manifest\nls ~/.config/pi/skills/my-skill  # not in manifest.skills\n// after\nmv ~/.config/pi/skills/my-skill ~/my-skills/my-skill\nbun run convert --to pi","handlingStrategy":"validation","validationCode":"import { existsSync, readFileSync } from 'node:fs'\nfunction isManifestManagedSkill(manifestPath: string, skillName: string): boolean {\n  if (!existsSync(manifestPath)) return false\n  const m = JSON.parse(readFileSync(manifestPath, 'utf8'))\n  return Array.isArray(m?.skills) && m.skills.includes(skillName)\n}\n// if false for an existing directory, cleanup will skip it as unmanaged","typeGuard":"function manifestListsSkill(m: unknown, skill: string): m is { skills: string[] } {\n  return typeof m === 'object' && m !== null &&\n    Array.isArray((m as any).skills) && (m as any).skills.includes(skill)\n}","tryCatchPattern":null,"preventionTips":["Put personal skills outside the plugin-managed skills root","Preserve the Pi install manifest; losing it orphans previous installs as 'unmanaged'","After plugin upgrades that rename skills, reinstall and then manually remove any renamed leftovers after backing them up"],"tags":["pi","skills","manifest","unmanaged-directory"],"backgroundTag":"unmanaged-file-not-overwritten","analyzedSha":"c9c10f8c75412c7232cb2bd663e5fd1cea98d84e","analyzedAt":"2026-08-31T15:18:07.959Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}