EveryInc/compound-engineering-plugin · error · Error
${context.collectionPath} exists and is not a symlink; refus
Error message
${context.collectionPath} exists and is not a symlink; refusing to remove it What it means
removeLocalCollection() removes the symlinked local skills collection but only if it manages that entry. A 'collision' state means the path exists as a regular file or real directory (not a symlink), so the code refuses to delete user data it did not create.
Source
Thrown at src/dev/codex-dev.ts:422
context: CodexDevContext,
previous: Extract<LocalCollectionState, { kind: "absent" | "valid" }>,
activatedTarget: string,
): Promise<void> {
if (previous.kind === "absent") {
await removeManagedCollectionLink(context.collectionPath, activatedTarget, { ignoreChanges: true })
return
}
await replaceManagedCollectionLink(context.collectionPath, previous.target, {
kind: "valid",
target: activatedTarget,
})
}
export async function removeLocalCollection(context: CodexDevContext): Promise<boolean> {
const state = await inspectLocalCollection(context)
if (state.kind === "absent") return false
if (state.kind === "collision") {
throw new Error(`${context.collectionPath} exists and is not a symlink; refusing to remove it`)
}
if (state.kind === "unrelated") {
throw new Error(`${context.collectionPath} points outside a Compound Engineering checkout; refusing to remove it`)
}
if (state.kind === "broken") {
throw new Error(`${context.collectionPath} is a broken symlink; refusing to remove it automatically`)
}
await removeManagedCollectionLink(context.collectionPath, state.target)
return true
}
function parseJson<T>(result: CommandResult, label: string): T {
try {
return JSON.parse(result.stdout) as T
} catch (error) {
throw new Error(`${label} returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`)
}
}View on GitHub (pinned to c9c10f8c75)
Solutions
- Inspect the path: `ls -la <collectionPath>` to confirm it is a directory/file you created yourself
- Back up anything valuable inside it before touching it
- Remove it manually (`rm -rf <collectionPath>` or `rm <collectionPath>`) once confirmed safe
- Re-run the codex:dev command, which will then manage a fresh symlink
Example fix
# before $ bun run codex:dev -- remote # Error: ... exists and is not a symlink; refusing to remove it # after $ ls -la /path/to/CODEX_HOME/skills/compound-engineering-local # verify it is yours $ mv /path/to/CODEX_HOME/skills/compound-engineering-local /tmp/ce-backup $ bun run codex:dev -- remote
Defensive patterns
Strategy: validation
Validate before calling
const state = await inspectLocalCollection(context);
if (state.kind === "collision") {
const stat = await fs.lstat(context.collectionPath);
console.warn(`${context.collectionPath} is a real ${stat.isDirectory() ? "directory" : "file"}; move it aside before removing`);
return;
} Type guard
function isSafeToRemove(s: { kind: string }): s is { kind: "absent" | "valid" | "unrelated" | "broken" } {
return s.kind !== "collision";
} Try / catch
try {
await removeLocalCollection(context);
} catch (e) {
if (String(e).includes("is not a symlink")) {
await fs.rename(context.collectionPath, context.collectionPath + ".bak"); // preserve user data
await removeLocalCollection(context);
} else throw e;
} Prevention
- Never hand-place real files at the collection path; always let codex:dev create the symlink
- Check `ls -la <collectionPath>` before switching modes
- Only delete non-symlink content you created and backed up
When it happens
Trigger: Calling removeLocalCollection() (directly or via switchToRemote() / removeCodexDevInstallation()) when collectionPath is a plain directory or file rather than the managed symlink — inspectLocalCollection returns kind='collision'.
Common situations: The user previously copied real skill files into the collection path instead of symlinking; a plugin manager materialized the directory; an earlier failed run replaced the symlink with a real directory.
Related errors
- ${context.collectionPath} points outside a Compound Engineer
- ${context.collectionPath} is a broken symlink; refusing to o
- ${collectionPath} changed since it was inspected; refusing t
- ${collectionPath} changed since it was inspected; refusing t
- ${context.collectionPath} is a broken symlink; refusing to r
AI-assisted analysis of EveryInc/compound-engineering-plugin@c9c10f8c75 (2026-08-31).
Data as JSON: /api/errors/e0c3eff5f8426032.
Report an issue: GitHub.