{"record":{"id":"47702da919e53a26","repo":"EveryInc/compound-engineering-plugin","slug":"context-collectionpath-is-a-broken-symlink-ref","errorCode":null,"errorMessage":"${context.collectionPath} is a broken symlink; refusing to overwrite it","messagePattern":"(.+?) is a broken symlink; refusing to overwrite it","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/dev/codex-dev.ts","lineNumber":272,"sourceCode":"    return { kind: \"valid\", target, resolvedTarget }\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { kind: \"broken\", target }\n    throw error\n  }\n}\n\nexport async function activateLocalCollection(context: CodexDevContext): Promise<void> {\n  await fs.mkdir(path.dirname(context.collectionPath), { recursive: true })\n  const desiredTarget = await fs.realpath(context.skillsRoot)\n  const state = await inspectLocalCollection(context)\n  if (state.kind === \"collision\") {\n    throw new Error(`${context.collectionPath} exists and is not a symlink; refusing to overwrite it`)\n  }\n  if (state.kind === \"unrelated\") {\n    throw new Error(`${context.collectionPath} points outside a Compound Engineering checkout; refusing to overwrite it`)\n  }\n  if (state.kind === \"broken\") {\n    throw new Error(`${context.collectionPath} is a broken symlink; refusing to overwrite it`)\n  }\n  if (state.kind === \"valid\" && state.resolvedTarget === desiredTarget) return\n\n  await replaceManagedCollectionLink(context.collectionPath, desiredTarget, state)\n}\n\nexport type ManagedCollectionLinkExpectation =\n  | { kind: \"absent\" }\n  | { kind: \"valid\"; target: string }\n\nexport async function removeManagedCollectionLink(\n  collectionPath: string,\n  expectedTarget: string,\n  options: {\n    ignoreChanges?: boolean\n    onTakenForTest?: (recoveryPath: string) => Promise<void>\n  } = {},\n): Promise<boolean> {","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/EveryInc/compound-engineering-plugin/blob/c9c10f8c75412c7232cb2bd663e5fd1cea98d84e/src/dev/codex-dev.ts#L254-L290","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","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"],"exampleFix":"# before\n$ rm -rf ~/code/compound-engineering   # checkout deleted, symlink dangles\n$ bun run codex:dev -- local\n# Error: .../skills/compound-engineering-local is a broken symlink; refusing to overwrite it\n\n# after\n$ rm /path/to/CODEX_HOME/skills/compound-engineering-local  # remove dangling link\n$ bun run codex:dev -- local                                  # recreates the link","handlingStrategy":"validation","validationCode":"import { inspectLocalCollection, type CodexDevContext } from \"<lib>/src/dev/codex-dev\";\nconst state = await inspectLocalCollection(context);\nif (state.kind === \"broken\") console.log(`dangling link -> ${state.target}; restore checkout or rm it first`);","typeGuard":"function isRemovableState(s: { kind: string }): boolean {\n  return s.kind === \"absent\" || s.kind === \"valid\";\n}","tryCatchPattern":"try {\n  await activateLocalCollection(context);\n} catch (e) {\n  if (String(e).includes(\"is a broken symlink\")) {\n    await fs.unlink(context.collectionPath); // dangling link: target is already gone\n    await activateLocalCollection(context);\n  } else throw e;\n}","preventionTips":["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"],"tags":["symlink","broken-symlink","filesystem","codex-dev"],"backgroundTag":"broken-symlink","analyzedSha":"c9c10f8c75412c7232cb2bd663e5fd1cea98d84e","analyzedAt":"2026-08-31T15:18:07.959Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}