affaan-m/ECC · error · Error

Refusing to overwrite an unowned or changed install-state at

Error message

Refusing to overwrite an unowned or changed install-state at ${plan.installStatePath}. Re-run the guided preview and review the existing state before retrying.

What it means

Thrown by assertInstallStateUnchanged when the managed install-state file (plan.installStatePath) no longer matches the SHA-256 fingerprint captured during preflight. The installer fingerprints the state file before planning and re-checks it immediately before each write (beforeOperationWrite and beforeInstallStateWrite), refusing to proceed if the bytes changed in between. This is a deliberate TOCTOU (time-of-check/time-of-use) guard: the install-state is the sole proof of which destinations ECC owns, so silently overwriting a mutated state could clobber files the user edited.

Source

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

}

function operationIdentityMatches(stateOperation, plannedOperation) {
  return [
    'kind',
    'moduleId',
    'sourceRelativePath',
    'strategy',
    'scaffoldOnly',
  ].every(field => stateOperation[field] === plannedOperation[field]);
}

function assertInstallStateUnchanged(plan, expectedFingerprint) {
  const currentFingerprint = fingerprintFile(plan.installStatePath);
  if (
    currentFingerprint.exists !== expectedFingerprint.exists
    || currentFingerprint.sha256 !== expectedFingerprint.sha256
  ) {
    throw new Error(
      `Refusing to overwrite an unowned or changed install-state at ${plan.installStatePath}. `
      + 'Re-run the guided preview and review the existing state before retrying.'
    );
  }
}

function assertPriorInstallStateMatchesPlan(state, plan) {
  const target = state.target || {};
  const adapter = plan.adapter || {};
  if (
    target.id !== adapter.id
    || target.target !== adapter.target
    || target.kind !== adapter.kind
  ) {
    throw new Error(
      `Refusing to trust managed install-state at ${plan.installStatePath}: `
      + 'target identity does not match the current Kimi install plan.'
    );

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Re-run the guided preview (createMultiHarnessPlan with dryRun) to regenerate the fingerprint against the current state, then apply immediately without touching the file.
  2. If the existing install-state is stale or corrupt, delete plan.installStatePath and re-run the preview so ECC records fresh ownership.
  3. Ensure no other ECC/install process, editor, or sync agent is touching the install-state between preview and apply.
  4. Inspect the file at plan.installStatePath to confirm whether the change was intentional before discarding it.

Example fix

// before: preview and apply separated by manual state edits
const preview = await createMultiHarnessPlan(req, {}, opts);
// user edits .claude/install-state.json here -> fingerprint drifts
await applyMultiHarnessPlan(preview, {}, opts); // throws [280]

// after: preview and apply back-to-back, no intervening mutation
const preview = await createMultiHarnessPlan(req, {}, opts);
await applyMultiHarnessPlan(preview, {}, opts);
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
function assertInstallStateStable(installStatePath, expectedFingerprint) {
  if (!installStatePath || !expectedFingerprint) return; // fresh install, nothing to check
  const exists = fs.existsSync(installStatePath);
  if (exists !== expectedFingerprint.exists) {
    throw new Error(`Install-state existence changed; re-run preview before apply.`);
  }
  if (exists) {
    const crypto = require('crypto');
    const sha = crypto.createHash('sha256').update(fs.readFileSync(installStatePath)).digest('hex');
    if (sha !== expectedFingerprint.sha256) {
      throw new Error(`Install-state digest drift; re-run preview before apply.`);
    }
  }
}
// call right before applyMultiHarnessPlan:
assertInstallStateStable(preview.harnesses.find(h=>h.id==='kimi').preview.plan.installStatePath,
  preview.harnesses.find(h=>h.id==='kimi').preview.ownershipSnapshot.stateFingerprint);

Type guard

null

Try / catch

try {
  await applyMultiHarnessPlan(preview, {}, options);
} catch (err) {
  if (/Refusing to overwrite an unowned or changed install-state/.test(err.message)) {
    // state drifted between preview and apply: re-preflight and retry once
    const repreview = await createMultiHarnessPlan(preview.request, {}, options);
    await applyMultiHarnessPlan(repreview, {}, options);
  } else throw err;
}

Prevention

When it happens

Trigger: Called inside applyPreflightedManagedPlan via assertStateUnchanged() at beforeOperationWrite and beforeInstallStateWrite. Fires when fingerprintFile(plan.installStatePath) returns exists/sha256 differing from preview.ownershipSnapshot.stateFingerprint. This happens if, between createMultiHarnessPlan->preflightManaged and applyMultiHarnessPlan->applyManaged, the install-state JSON was edited, deleted, rewritten by another ECC process, touched by a sync tool (Dropbox/iCloud/git checkout), or the path started returning a different realpath.

Common situations: Running two guided installs concurrently in the same project; a previous partial install left a half-written state; the user hand-edited .claude/install-state.json; a git branch switch or filesystem sync changed the file between preview and apply; symlinks resolving differently between runs.

Related errors


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