EveryInc/compound-engineering-plugin · error · Error

${collectionPath} changed since it was inspected; refusing t

Error message

${collectionPath} changed since it was inspected; refusing to remove it. The entry taken for validation is preserved at ${recoveryPath}; the current entry at ${collectionPath} was not overwritten (${reason})

What it means

If restore-after-validation fails inside removeManagedCollectionLink(), restorationFailed() re-raises a combined error: the path changed since inspection, the validated entry is preserved at recoveryPath, and the current entry at collectionPath was left untouched. It distinguishes this late-failure case from the early changed() abort so the caller knows exactly which copy lives where.

Source

Thrown at src/dev/codex-dev.ts:336

        `The entry is preserved at ${recoveryPath} (${reason})`,
    )
  }

  const stat = await fs.lstat(recoveryPath)
  let actualTarget: string | undefined
  if (stat.isSymbolicLink()) {
    actualTarget = await fs.readlink(recoveryPath)
  }

  if (stat.isSymbolicLink() && actualTarget === expectedTarget) {
    await fs.unlink(recoveryPath)
    await fs.rmdir(recoveryDir)
    return true
  }

  const restorationFailed = (error: unknown): never => {
    const reason = error instanceof Error ? error.message : String(error)
    throw new Error(
      `${collectionPath} changed since it was inspected; refusing to remove it. ` +
        `The entry taken for validation is preserved at ${recoveryPath}; ` +
        `the current entry at ${collectionPath} was not overwritten (${reason})`,
    )
  }

  if (stat.isSymbolicLink()) {
    try {
      await fs.symlink(actualTarget!, collectionPath)
    } catch (error) {
      restorationFailed(error)
    }
    await fs.unlink(recoveryPath)
    await fs.rmdir(recoveryDir)
    return changed()
  }

  if (stat.isFile()) {

View on GitHub (pinned to c9c10f8c75)

Solutions

  1. Locate the intact original at recoveryPath from the error message
  2. Check what is now at collectionPath (`ls -la <collectionPath>`) and decide which copy to keep
  3. Stop concurrent codex:dev / sync processes, then re-run the removal or manually place the recovery copy
  4. Re-run `bun run codex:dev -- status` to confirm final state

Example fix

# before (race)
$ bun run codex:dev -- remote   # another terminal: rm -rf CODEX_HOME/skills/... mid-run
# Error: ... changed since it was inspected; ... preserved at <recoveryPath>

# after
$ # close other sessions/sync tools, restore from recoveryPath if needed
$ mv "$recoveryPath" "$collectionPath"
$ bun run codex:dev -- remote
Defensive patterns

Strategy: try-catch

Validate before calling

// verify nothing else mutates the path during removal
const watcher = fs.watch(path.dirname(collectionPath));
watcher.on("change", () => console.warn("path changing during removal"));

Try / catch

try {
  await removeLocalCollection(context);
} catch (e) {
  if (String(e).includes("changed since it was inspected")) {
    const m = String(e).match(/preserved at (\S+)/);
    if (m) await fs.rename(m[1], context.collectionPath); // put the taken entry back
  } else throw e;
}

Prevention

When it happens

Trigger: During the restore phase of removal, restoring the taken entry to collectionPath fails (EEXIST, EACCES, ENOENT) because something appeared or changed at collectionPath after it was inspected and taken.

Common situations: Another process recreated the skills path while the removal was mid-flight (TOCTOU race); antivirus or file-sync software recreating the directory; the user editing the path during a long-running command.

Related errors


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