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
- Close other processes that may write the install-state (other ECC runs, editors with the file open, sync clients) and retry.
- Re-run the guided preview to capture a stable fingerprint and apply immediately.
- Move the project out of a synced directory during install, or pause sync.
- 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
- Serialize installs: never run two ECC installs against the same project at once.
- Close editors and pause sync clients that may rewrite .claude/install-state.json.
- On Windows, exclude the project from antivirus scanning during install if it rewrites files.
- If a prior ECC run crashed, verify no orphan process is still writing before retrying.
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
- Refusing to overwrite an unowned or changed install-state at
- ${label} must remain a regular, non-symlink file while it is
- ${label} is too large (maximum ${maxBytes} bytes).
- Memory destination changed while it was being created.
- Refusing to trust managed install-state at ${plan.installSta
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/dd3536e4cf400242.
Report an issue: GitHub.