EveryInc/compound-engineering-plugin · error · Error

${context.collectionPath} is a broken symlink; refusing to r

Error message

${context.collectionPath} is a broken symlink; refusing to remove it automatically

What it means

removeLocalCollection() encounters a dangling symlink (target no longer exists) and refuses to remove it automatically. Unlike valid links, a broken link cannot be verified as Compound Engineering-owned, so an explicit manual step is required instead of a silent auto-delete.

Source

Thrown at src/dev/codex-dev.ts:428

    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)}`)
  }
}

async function runCodex(
  context: CodexDevContext,
  runner: CommandRunner,
  args: string[],
): Promise<CommandResult> {

View on GitHub (pinned to c9c10f8c75)

Solutions

  1. Confirm the target is truly gone: `readlink <collectionPath>` then test the target path
  2. Remove the dangling link manually: `rm <collectionPath>`
  3. Restore the checkout first instead (re-clone / remount) if you want to keep local mode
  4. Re-run the codex:dev command to establish the desired state

Example fix

# before
$ bun run codex:dev -- remote
# Error: ... is a broken symlink; refusing to remove it automatically

# after
$ rm /path/to/CODEX_HOME/skills/compound-engineering-local  # target already gone
$ bun run codex:dev -- remote
Defensive patterns

Strategy: try-catch

Validate before calling

const state = await inspectLocalCollection(context);
if (state.kind === "broken") {
  console.log(`dangling link to ${state.target}; safe to rm the link`);
}

Type guard

function isDanglingLink(s: { kind: string; target?: string }): s is { kind: "broken"; target: string } {
  return s.kind === "broken" && typeof s.target === "string";
}

Try / catch

try {
  await removeLocalCollection(context);
} catch (e) {
  if (String(e).includes("broken symlink")) {
    await fs.unlink(context.collectionPath); // target already gone; deleting the link is safe
    await removeLocalCollection(context);
  } else throw e;
}

Prevention

When it happens

Trigger: removeLocalCollection() (or switchToRemote / removeCodexDevInstallation) is called when collectionPath is a symlink whose realpath raises ENOENT — inspectLocalCollection returns kind='broken'.

Common situations: The linked checkout was deleted or moved after a previous local install; skillsRoot was on a removable/unmounted volume; a build script wiped the skills directory but left the symlink.

Related errors


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