Fission-AI/OpenSpec · error
A concurrent file appeared at
Error message
A concurrent file appeared at ${update.target} while archive was retiring it. What it means
Thrown during deferred retirement in the specs-apply retire flow: after renaming the spec file aside to a displaced path, an lstat on the original target unexpectedly succeeded — meaning another process created a file at that path between the rename and the check. The retirement aborts rather than clobbering the newcomer.
Solutions
- Identify the concurrent writer (editor sync, another openspec process) and stop it, then rerun the archive
- Manually reconcile the concurrently-created file at the target path
- Re-run the archive after the concurrent operation completes
Defensive patterns
Strategy: try-catch
When it happens
Trigger: Thrown at src/core/specs-apply.ts:843 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of Fission-AI/OpenSpec@6926ccb18a (2026-08-25).
Data as JSON: /api/errors/edd83a8fd5437fc0.
Report an issue: GitHub.
Appendix: source
Thrown at src/core/specs-apply.ts:843
throw new Error(
`Could not retire capability '${update.id}': ${update.target} resolves outside ` +
`${mainSpecsDir}. Remove the external file by hand, or replace the symlink and rerun.`
);
}
}
let displacedPath: string | undefined;
try {
await options.beforeMutate?.();
if (options.verifyDisplaced) {
const displaced = `${update.target}.openspec-retire-${randomUUID()}`;
displacedPath = displaced;
await fs.rename(update.target, displaced);
try {
await options.verifyDisplaced(displaced);
try {
await fs.lstat(update.target);
throw new Error(
`A concurrent file appeared at ${update.target} while archive was retiring it.`
);
} catch (targetError) {
if ((targetError as NodeJS.ErrnoException).code !== 'ENOENT') throw targetError;
}
if (!options.deferDelete) await fs.unlink(displaced);
} catch (error) {
try {
await fs.lstat(update.target);
throw new Error(
`${error instanceof Error ? error.message : String(error)} ` +
`A concurrent file now occupies ${update.target}; the displaced spec was retained at ${displaced}.`
);
} catch (targetError) {
if ((targetError as NodeJS.ErrnoException).code !== 'ENOENT') throw targetError;
}
await fs.rename(displaced, update.target);
throw error;View on GitHub (pinned to 6926ccb18a)