affaan-m/ECC · error · Error

Refusing to trust install-state that changed during validati

Error message

Refusing to trust install-state that changed during validation: ${plan.installStatePath}.

What it means

Thrown by readOwnedDestinations as a TOCTOU guard around the read itself. The installer fingerprints the install-state (initialFingerprint), calls readInstallState, then fingerprints again (validatedFingerprint); if the two fingerprints differ, the file was modified while being read and the parsed state cannot be trusted. JSON parsers tolerate partial writes, so this check is what catches a concurrent mutation that would otherwise produce silently wrong ownership data.

Source

Thrown at scripts/lib/multi-harness-setup.js:145

    return { destinations: new Set(), stateFingerprint: { exists: false, sha256: null } };
  }
  try {
    assertSafeInstallOperation(plan, { destinationPath: plan.installStatePath });
  } catch (error) {
    throw new Error(`Refusing to trust managed install-state path: ${error.message}`);
  }
  if (!fs.existsSync(plan.installStatePath)) {
    return { destinations: new Set(), stateFingerprint: { exists: false, sha256: null } };
  }
  const readState = dependencies.readInstallState || require('./install-state').readInstallState;
  const initialFingerprint = fingerprintFile(plan.installStatePath);
  const state = readState(plan.installStatePath);
  const validatedFingerprint = fingerprintFile(plan.installStatePath);
  if (
    initialFingerprint.exists !== validatedFingerprint.exists
    || initialFingerprint.sha256 !== validatedFingerprint.sha256
  ) {
    throw new Error(
      `Refusing to trust install-state that changed during validation: ${plan.installStatePath}.`
    );
  }
  assertPriorInstallStateMatchesPlan(state, plan);
  const plannedByDestination = new Map(plan.operations.map(operation => [
    canonicalPath(operation.destinationPath),
    operation,
  ]));
  const destinations = new Set();
  for (const operation of state.operations || []) {
    if (operation.ownership !== 'managed') {
      throw new Error(
        `Refusing to trust non-managed ownership from install-state at ${plan.installStatePath}.`
      );
    }
    const destinationPath = operation.destinationPath;
    assertWithinTrustedRoot(destinationPath, plan.targetRoot, 'trust install-state ownership');
    const canonicalDestination = canonicalPath(destinationPath);

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Close other processes that may write the install-state (other ECC runs, editors with the file open, sync clients) and retry.
  2. Re-run the guided preview to capture a stable fingerprint and apply immediately.
  3. Move the project out of a synced directory during install, or pause sync.
  4. If it recurs, check for a crashed prior ECC process still holding/rewriting the file.

Example fix

// Hard to show a code fix; this is an environmental race. Mitigate by serializing installs:

// before: two concurrent installs race the same state file
// terminal A: applyMultiHarnessPlan(planA)  // writes install-state
// terminal B: applyMultiHarnessPlan(planB)  // throws [285]

// after: run one install at a time per project
await applyMultiHarnessPlan(planA); // completes, releases state
// only then:
await applyMultiHarnessPlan(planB);
Defensive patterns

Strategy: retry

Validate before calling

const fs = require('fs'); const crypto = require('crypto');
function fingerprint(p) {
  if (!fs.existsSync(p)) return { exists: false, sha256: null };
  return { exists: true, sha256: crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex') };
}
function assertStateQuiescent(statePath) {
  const a = fingerprint(statePath);
  const b = fingerprint(statePath);
  if (a.exists !== b.exists || a.sha256 !== b.sha256) {
    throw new Error(`Install-state is being modified concurrently; stop other writers and retry.`);
  }
}
assertStateQuiescent(plan.installStatePath);

Type guard

null

Try / catch

async function applyWithQuiescence(plan, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await applyMultiHarnessPlan(plan);
    } catch (err) {
      if (/changed during validation/.test(err.message) && i < attempts - 1) continue;
      throw err;
    }
  }
}

Prevention

When it happens

Trigger: Fires when initialFingerprint and validatedFingerprint (exists or sha256) differ inside readOwnedDestinations. Caused by another process writing the install-state between the two fingerprintFile calls: a concurrent ECC run, a file watcher/linter rewriting JSON, an editor autosave, a sync tool, or a partial write from a crashed previous run being flushed.

Common situations: Two terminal sessions running guided install in parallel; an IDE formatting/saving .claude/install-state.json during install; cloud-sync (Dropbox/OneDrive) rewriting the file; a previous ECC process crashed mid-write and the OS is finalizing; antivirus locking/rewriting the file on Windows.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/dd3536e4cf400242. Report an issue: GitHub.