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

  1. Inspect the path: `ls -la <collectionPath>` to confirm it is a directory/file you created yourself
  2. Back up anything valuable inside it before touching it
  3. Remove it manually (`rm -rf <collectionPath>` or `rm <collectionPath>`) once confirmed safe
  4. 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

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


AI-assisted analysis of EveryInc/compound-engineering-plugin@c9c10f8c75 (2026-08-31). Data as JSON: /api/errors/e0c3eff5f8426032. Report an issue: GitHub.