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?: boolean

View on GitHub (pinned to c9c10f8c75)

Solutions

  1. Check where it points: readlink ~/.codex/skills/compound-engineering-local and confirm the target path.
  2. If the target is a deleted or abandoned checkout, remove just the link: rm ~/.codex/skills/compound-engineering-local, then re-run local mode.
  3. 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.
  4. 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

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


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