Fission-AI/OpenSpec · error · Error
Could not track the displaced main spec for
Error message
Could not track the displaced main spec for '${p.update.id}' during retirement. What it means
Thrown when the displaced (moved-aside) main spec file's movable-path fingerprint no longer matches the fingerprint recorded at preparation, checked inside the retirement beforeMutate hook. Means the spec that is about to be retired was modified or replaced while archive was securing it; retirement is aborted so newer content is not destroyed.
Solutions
- Check for locks or concurrent access on the displaced spec backup and re-run
- Inspect the retirement backup directory state manually if the abort recurs
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at src/core/archive.ts:1814 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/2c96678d1a85dfdf.
Report an issue: GitHub.
Appendix: source
Thrown at src/core/archive.ts:1814
changeDir,
retirementAuthorizationFingerprint!
);
if (
(await fingerprintMovablePath(displacedPath)) !==
p.targetMovableFingerprint
) {
throw new Error(
`Main spec '${p.update.id}' changed while archive was securing it for retirement.`
);
}
},
...(isStoreSelectedRoot(root) ? { displayPath: p.update.target } : {}),
}
);
if (!retired) continue;
const retirementSnapshot = specSnapshotsByTarget.get(p.update.target);
if (retirementSnapshot === undefined || displacedPath === undefined) {
throw new Error(
`Could not track the displaced main spec for '${p.update.id}' during retirement.`
);
}
retirementSnapshot.displacedPath = displacedPath;
retirementSnapshot.displacedFingerprint = p.targetMovableFingerprint;
wroteAny = true;
// A rename applied on the way to the retirement still happened;
// folding every count in keeps the totals honest about the whole
// delta.
writeTotals.added += p.counts.added;
writeTotals.modified += p.counts.modified;
writeTotals.removed += p.counts.removed;
writeTotals.renamed += p.counts.renamed;
// Deleting a file is the one archive outcome a JSON consumer cannot
// infer from the totals, so it is recorded the way every other
// spec-merge divergence is. Purpose always goes with the file, so it
// is named too rather than left to the reader to work out, and the
// note carries the command that brings the file back.View on GitHub (pinned to 6926ccb18a)