EveryInc/compound-engineering-plugin · error · Error
${context.collectionPath} is a broken symlink; refusing to o
Error message
${context.collectionPath} is a broken symlink; refusing to overwrite it What it means
activateLocalCollection() links the user's Codex skills collection to this checkout's skills directory. Before creating or replacing the symlink it inspects the current path and refuses to clobber anything it does not fully own. A 'broken' state means the path is a symlink whose target no longer exists, and the code deliberately stops instead of silently replacing it.
Source
Thrown at src/dev/codex-dev.ts:272
return { kind: "valid", target, resolvedTarget }
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") return { kind: "broken", target }
throw error
}
}
export async function activateLocalCollection(context: CodexDevContext): Promise<void> {
await fs.mkdir(path.dirname(context.collectionPath), { recursive: true })
const desiredTarget = await fs.realpath(context.skillsRoot)
const state = await inspectLocalCollection(context)
if (state.kind === "collision") {
throw new Error(`${context.collectionPath} exists and is not a symlink; refusing to overwrite it`)
}
if (state.kind === "unrelated") {
throw new Error(`${context.collectionPath} points outside a Compound Engineering checkout; refusing to overwrite it`)
}
if (state.kind === "broken") {
throw new Error(`${context.collectionPath} is a broken symlink; refusing to overwrite it`)
}
if (state.kind === "valid" && state.resolvedTarget === desiredTarget) return
await replaceManagedCollectionLink(context.collectionPath, desiredTarget, state)
}
export type ManagedCollectionLinkExpectation =
| { kind: "absent" }
| { kind: "valid"; target: string }
export async function removeManagedCollectionLink(
collectionPath: string,
expectedTarget: string,
options: {
ignoreChanges?: boolean
onTakenForTest?: (recoveryPath: string) => Promise<void>
} = {},
): Promise<boolean> {View on GitHub (pinned to c9c10f8c75)
Solutions
- Find where the symlink points with `ls -la <collectionPath>` and confirm the checkout location
- Restore the missing checkout (re-clone, move back, or remount the volume) so the symlink resolves
- If the target is genuinely gone, delete the dangling symlink manually (`rm <collectionPath>`) and re-run the switch-to-local command
- Run `bun run codex:dev -- status` to see the current collection state before retrying
Example fix
# before $ rm -rf ~/code/compound-engineering # checkout deleted, symlink dangles $ bun run codex:dev -- local # Error: .../skills/compound-engineering-local is a broken symlink; refusing to overwrite it # after $ rm /path/to/CODEX_HOME/skills/compound-engineering-local # remove dangling link $ bun run codex:dev -- local # recreates the link
Defensive patterns
Strategy: validation
Validate before calling
import { inspectLocalCollection, type CodexDevContext } from "<lib>/src/dev/codex-dev";
const state = await inspectLocalCollection(context);
if (state.kind === "broken") console.log(`dangling link -> ${state.target}; restore checkout or rm it first`); Type guard
function isRemovableState(s: { kind: string }): boolean {
return s.kind === "absent" || s.kind === "valid";
} Try / catch
try {
await activateLocalCollection(context);
} catch (e) {
if (String(e).includes("is a broken symlink")) {
await fs.unlink(context.collectionPath); // dangling link: target is already gone
await activateLocalCollection(context);
} else throw e;
} Prevention
- Run `bun run codex:dev -- status` before switching modes
- Keep the linked checkout at a stable path (avoid ad-hoc renames)
- Do not delete or move a checkout that is the target of the collection symlink
When it happens
Trigger: Calling switchToLocal() (or activateLocalCollection directly) when context.collectionPath is a symlink pointing at a deleted directory — realpath() resolves with ENOENT, so inspectLocalCollection returns kind='broken'.
Common situations: The linked Compound Engineering checkout was deleted, renamed, or moved after a previous `codex:dev local`; the skillsRoot directory was removed or is on an unmounted volume; a stale symlink left behind by a partial cleanup.
Related errors
- ${context.collectionPath} is a broken symlink; refusing to r
- ${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/47702da919e53a26.
Report an issue: GitHub.