EveryInc/compound-engineering-plugin · warning
Skipping ${targetDir}: existing user-managed symlink (not ov
Error message
Skipping ${targetDir}: existing user-managed symlink (not overwritten) What it means
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.
Source
Thrown at src/targets/pi.ts:462
if (!isSafeManagedPath(agentsDir, agentFile)) continue
const targetPath = path.join(agentsDir, agentFile)
if (await isPreservedSymlink(targetPath)) continue
await fs.rm(targetPath, { force: true })
}
}
// Returns true when the existing path was preserved (skip cleanup AND the
// subsequent copy/write -- writing through a preserved symlink would clobber
// the user's fork, which is worse than not cleaning up at all).
async function cleanupCurrentManagedSkillDir(
targetDir: string,
manifest: PiInstallManifest | null,
skillName: string,
): Promise<boolean> {
const stat = await lstatOrNull(targetDir)
if (!stat) return false
if (stat.isSymbolicLink()) {
console.warn(`Skipping ${targetDir}: existing user-managed symlink (not overwritten)`)
return true
}
if (!manifest?.skills.includes(skillName)) {
console.warn(`Skipping ${targetDir}: existing unmanaged directory (not overwritten)`)
return true
}
await fs.rm(targetDir, { recursive: true, force: true })
return false
}
async function cleanupCurrentManagedAgentFile(
targetPath: string,
manifest: PiInstallManifest | null,
agentFileName: string,
): Promise<boolean> {
const stat = await lstatOrNull(targetPath)
if (!stat) return false
if (stat.isSymbolicLink()) {View on GitHub (pinned to c9c10f8c75)
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.
Example fix
// before ls -la ~/.config/pi/skills/ce-plan # -> ~/dotfiles/pi-skills/ce-plan // after rm ~/.config/pi/skills/ce-plan bun run convert --to pi
Defensive patterns
Strategy: validation
Validate before calling
import { lstat } from 'node:fs/promises'
async function skillDirIsSymlink(root: string, skillName: string): Promise<boolean> {
try { return (await lstat(path.join(root, skillName))).isSymbolicLink() } catch { return false }
}
// before install: if (await skillDirIsSymlink(skillsRoot, name)) remove or accept the skip Type guard
function statIsSymlink(stat: { isSymbolicLink(): boolean } | null): boolean {
return stat?.isSymbolicLink() === true
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Skipping ${targetPath}: existing user-managed symlink (not o
- Skipping ${storeDir}: resolves outside the managed root via
- Skipping ${targetPath}: existing user-managed symlink (not o
- Skipping ${targetDir}: existing unmanaged directory (not ove
- No Codex skills were found under ${skillsRoot}
AI-assisted analysis of EveryInc/compound-engineering-plugin@c9c10f8c75 (2026-08-31).
Data as JSON: /api/errors/e6b9486cd6f59066.
Report an issue: GitHub.