affaan-m/ECC · error · Error

Refusing to write : destination changed after Kimi…

Error message

Refusing to write ${operation.destinationPath}: destination changed after Kimi preflight.

What it means

assertOperationUnchanged re-verifies, at write time, that each destination's kind, canonical path, and classification still match what was captured during the Kimi preflight. If the file changed (modified, deleted, replaced by a symlink/directory, or reclassified) between preflight and write, it refuses to write to prevent clobbering user edits or a TOCTOU race.

Solutions

  1. Re-run the install so preflight and write happen back-to-back on a stable tree
  2. Ensure no concurrent process modifies the destination during install (stop watchers, other agents, package managers)
  3. Check for symlinks or mount points that change canonical path and remove/adjust them
  4. If the change is intentional, review the file first, then re-run preflight

Example fix

// before
# editor/formatter rewrote ~/.kimi/config during install -> error
// after
# close file watchers / pause format-on-save, then re-run the managed install
Defensive patterns

Strategy: retry

Try / catch

try {
  await applyMultiHarnessPlan(plan);
} catch (e) {
  if (e.message.includes('changed after Kimi preflight')) {
    // stop other writers, then rebuild a fresh plan and retry once
    const fresh = await createMultiHarnessPlan(request);
    await applyMultiHarnessPlan(fresh);
  } else throw e;
}

Prevention

When it happens

Trigger: A file at operation.destinationPath was edited, regenerated (e.g. by a concurrent install or a formatter), deleted, or its type changed (file<->symlink<->directory) after preflightManagedPlan ran but before the operation executed.

Common situations: Another process (git checkout, npm install, another agent/harness) touching config files mid-install; running two installs in parallel; user editing the config while the installer runs; slow installs on large projects increasing the race window.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/26f7fe4a3dcccc43. Report an issue: GitHub.

Appendix: source

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

  const prepareInstallStateWrite = ({ state }) => {
    assertStateUnchanged();
    expectedStateFingerprint = fingerprintInstallStateValue(state);
  };
  const assertOperationUnchanged = operation => {
    const destination = canonicalPath(operation.destinationPath);
    const expected = expectedOperations.get(operation);
    const currentClassification = classifyManagedOperation(operation, ownedDestinations);
    const expectedClassification = operation.kind === 'merge-json'
      && writtenDestinations.has(destination)
      ? 'managed-json-update'
      : expected && expected.classification;
    if (
      !expected
      || expected.kind !== operation.kind
      || canonicalPath(expected.destinationPath) !== destination
      || expectedClassification !== currentClassification
    ) {
      throw new Error(
        `Refusing to write ${operation.destinationPath}: destination changed after Kimi preflight.`
      );
    }
    return destination;
  };

  const result = require('./install-executor').applyInstallPlan(preview.plan, {
    beforeInstallStateRead() {
      assertStateUnchanged();
      // Check the original preview before ownership filtering can skip a late
      // collision. Guided setup must report the changed plan as a failure.
      for (const operation of preview.plan.operations) {
        assertOperationUnchanged(operation);
      }
    },
    beforeOperationWrite({ operation }) {
      assertStateUnchanged();
      const destination = assertOperationUnchanged(operation);

View on GitHub (pinned to 8321021c54)