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
- Confirm the target is truly gone: `readlink <collectionPath>` then test the target path
- Remove the dangling link manually: `rm <collectionPath>`
- Restore the checkout first instead (re-clone / remount) if you want to keep local mode
- 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
- Do not delete checkouts that are symlink targets without first switching to remote
- Re-point or recreate links after moving a checkout
- Run `bun run codex:dev -- status` to detect broken state early
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
- ${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} exists and is not a symlink; refus
- ${context.collectionPath} points outside a Compound Engineer
AI-assisted analysis of EveryInc/compound-engineering-plugin@c9c10f8c75 (2026-08-31).
Data as JSON: /api/errors/b53b8bbea6d354fb.
Report an issue: GitHub.