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

  1. Check for locks or concurrent access on the displaced spec backup and re-run
  2. 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)