{"record":{"id":"5adcabdb5c9ef70a","repo":"EveryInc/compound-engineering-plugin","slug":"collectionpath-changed-since-it-was-inspected-5adcab","errorCode":null,"errorMessage":"${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})","messagePattern":"(.+?) changed since it was inspected; refusing to remove it\\. The entry taken for validation is preserved at (.+?); the current entry at (.+?) was not overwritten \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/dev/codex-dev.ts","lineNumber":336,"sourceCode":"        `The entry is preserved at ${recoveryPath} (${reason})`,\n    )\n  }\n\n  const stat = await fs.lstat(recoveryPath)\n  let actualTarget: string | undefined\n  if (stat.isSymbolicLink()) {\n    actualTarget = await fs.readlink(recoveryPath)\n  }\n\n  if (stat.isSymbolicLink() && actualTarget === expectedTarget) {\n    await fs.unlink(recoveryPath)\n    await fs.rmdir(recoveryDir)\n    return true\n  }\n\n  const restorationFailed = (error: unknown): never => {\n    const reason = error instanceof Error ? error.message : String(error)\n    throw new Error(\n      `${collectionPath} changed since it was inspected; refusing to remove it. ` +\n        `The entry taken for validation is preserved at ${recoveryPath}; ` +\n        `the current entry at ${collectionPath} was not overwritten (${reason})`,\n    )\n  }\n\n  if (stat.isSymbolicLink()) {\n    try {\n      await fs.symlink(actualTarget!, collectionPath)\n    } catch (error) {\n      restorationFailed(error)\n    }\n    await fs.unlink(recoveryPath)\n    await fs.rmdir(recoveryDir)\n    return changed()\n  }\n\n  if (stat.isFile()) {","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/EveryInc/compound-engineering-plugin/blob/c9c10f8c75412c7232cb2bd663e5fd1cea98d84e/src/dev/codex-dev.ts#L318-L354","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Locate the intact original at recoveryPath from the error message","Check what is now at collectionPath (`ls -la <collectionPath>`) and decide which copy to keep","Stop concurrent codex:dev / sync processes, then re-run the removal or manually place the recovery copy","Re-run `bun run codex:dev -- status` to confirm final state"],"exampleFix":"# before (race)\n$ bun run codex:dev -- remote   # another terminal: rm -rf CODEX_HOME/skills/... mid-run\n# Error: ... changed since it was inspected; ... preserved at <recoveryPath>\n\n# after\n$ # close other sessions/sync tools, restore from recoveryPath if needed\n$ mv \"$recoveryPath\" \"$collectionPath\"\n$ bun run codex:dev -- remote","handlingStrategy":"try-catch","validationCode":"// verify nothing else mutates the path during removal\nconst watcher = fs.watch(path.dirname(collectionPath));\nwatcher.on(\"change\", () => console.warn(\"path changing during removal\"));","typeGuard":null,"tryCatchPattern":"try {\n  await removeLocalCollection(context);\n} catch (e) {\n  if (String(e).includes(\"changed since it was inspected\")) {\n    const m = String(e).match(/preserved at (\\S+)/);\n    if (m) await fs.rename(m[1], context.collectionPath); // put the taken entry back\n  } else throw e;\n}","preventionTips":["Serialize all codex:dev mutations (no concurrent local/remote switches)","Exclude CODEX_HOME from real-time sync/antivirus scanning during operations","Treat the recoveryPath in the message as the authoritative copy of the original entry"],"tags":["race-condition","filesystem","symlink","recovery","codex-dev"],"backgroundTag":"path-changed-race-condition","analyzedSha":"c9c10f8c75412c7232cb2bd663e5fd1cea98d84e","analyzedAt":"2026-08-31T15:18:07.959Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}