EveryInc/compound-engineering-plugin · error · Error
${context.collectionPath} points outside a Compound Engineer
Error message
${context.collectionPath} points outside a Compound Engineering checkout; refusing to overwrite it What it means
When the collection path is a symlink, the library verifies it resolves inside the current Compound Engineering checkout (to <repoRoot>/skills). A symlink pointing somewhere else is kind "unrelated": overwriting could destroy a link the user or another tool owns, so activateLocalCollection refuses. This is a deliberate path-safety guard on user-level Codex state.
Source
Thrown at src/dev/codex-dev.ts:269
if (path.basename(resolvedTarget) !== "skills") {
return { kind: "unrelated", target, resolvedTarget }
}
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?: booleanView on GitHub (pinned to c9c10f8c75)
Solutions
- Check where it points: readlink ~/.codex/skills/compound-engineering-local and confirm the target path.
- If the target is a deleted or abandoned checkout, remove just the link: rm ~/.codex/skills/compound-engineering-local, then re-run local mode.
- Run the workflow from the checkout the link should point at (bun run codex:dev -- local from the new clone) — the link is only rewritten from the owning checkout.
- If two checkouts must coexist, remove the link each time before switching, or keep one canonical checkout for local mode.
Example fix
// before ~/.codex/skills/compound-engineering-local -> /old/path/compound-engineering/skills // deleted clone // after rm ~/.codex/skills/compound-engineering-local cd ~/src/compound-engineering && bun run codex:dev -- local # link now -> ~/src/compound-engineering/skills
Defensive patterns
Strategy: validation
Validate before calling
import { lstat, realpath } from "node:fs/promises"
import path from "node:path"
const p = path.join(process.env.CODEX_HOME || path.join(process.env.HOME!, ".codex"), "skills", "compound-engineering-local")
try {
const target = await realpath(p)
const here = await realpath("skills")
if (target !== here) console.warn(`Link points to ${target}, not this checkout; rm the link before switching checkouts`)
} catch { /* absent or dangling: fine */ } Try / catch
try {
await activateLocalCollection(ctx)
} catch (error) {
if ((error as Error).message.includes("points outside a Compound Engineering checkout")) {
console.error(`rm ${ctx.collectionPath} (stale link from another checkout) and retry`)
} else throw error
} Prevention
- Keep one canonical checkout for local mode; don't alternate between clones.
- After deleting or moving a checkout, rm the stale ~/.codex/skills/compound-engineering-local link.
- readlink the collection path when switching machines, clones, or worktrees.
When it happens
Trigger: inspectLocalCollection finds a symlink at <codexHome>/skills/compound-engineering-local whose realpath is not the current checkout's skillsRoot — e.g. it was created from a different clone, worktree, or an entirely unrelated project (src/dev/codex-dev.ts:269).
Common situations: Switching between two clones of the repo on one machine; the previous checkout was deleted and a new clone lives at a different path (resolvedTarget no longer matches); another project set up its own compound-engineering-local link.
Related errors
- ${context.collectionPath} exists and is not a symlink; refus
- The Codex plugin manifest does not point at this repository'
- ${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
AI-assisted analysis of EveryInc/compound-engineering-plugin@c9c10f8c75 (2026-08-31).
Data as JSON: /api/errors/1c946cae86fd5903.
Report an issue: GitHub.