affaan-m/ECC · error · Error

Refusing to write ${operation.destinationPath}: destination

Error message

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

What it means

Thrown by applyPreflightedManagedPlan's beforeOperationWrite hook when the operation being written differs from what preflight authorized. The hook re-classifies the operation (classifyManagedOperation) and compares kind, canonical destination, and classification against the expected entry from preview.operations at the same index. A mismatch means the destination or its ownership state changed between preflight and apply — a TOCTOU condition the installer refuses to silently accept.

Source

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

  const expectedStateFingerprint = preview.ownershipSnapshot.stateFingerprint;
  let operationIndex = 0;
  const assertStateUnchanged = () => (
    assertInstallStateUnchanged(preview.plan, expectedStateFingerprint)
  );

  return require('./install-executor').applyInstallPlan(preview.plan, {
    beforeOperationWrite({ operation }) {
      assertStateUnchanged();
      const expected = preview.operations[operationIndex];
      const currentClassification = classifyManagedOperation(operation, ownedDestinations);
      const destination = canonicalPath(operation.destinationPath);
      if (
        !expected
        || expected.kind !== operation.kind
        || canonicalPath(expected.destinationPath) !== destination
        || expected.classification !== currentClassification
      ) {
        throw new Error(
          `Refusing to write ${operation.destinationPath}: destination changed after Kimi preflight.`
        );
      }
      ownedDestinations.add(destination);
      operationIndex += 1;
    },
    beforeInstallStateWrite: assertStateUnchanged,
  });
}

function defaultDependencies(options = {}) {
  return {
    previewClaude: request => require('../setup').reconcileClaudePlugin(
      { dryRun: true, hooks: request.claudeHooks, scope: request.claudeScope }
    ),
    previewCodex: () => require('./codex-plugin-setup').reconcileCodexPlugin({ dryRun: true }),
    createManagedPlan: request => require('./install/runtime').createInstallPlanFromRequest(
      require('./install/request').normalizeInstallRequest({

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Re-run preflightManaged immediately before apply so the preview reflects the current filesystem, then apply without delay.
  2. Do not mutate plan.operations or plan.harnesses between preview and apply.
  3. Stop concurrent processes that touch destination paths during install.
  4. If the classification changed because a file now exists, decide intentionally: remove the file (back to 'create') or let ECC own it, then re-preflight.

Example fix

// before: file appears between preflight and apply, classification drifts
const preview = preflightManagedPlan(plan); // op classified 'create' (file absent)
fs.writeFileSync(destPath, '{}');         // file now exists -> re-classify 'json-merge'
applyPreflightedManagedPlan({ preview });  // throws [294]

// after: preflight again right before apply
const preview = preflightManagedPlan(plan);
// no intervening filesystem mutation
applyPreflightedManagedPlan({ preview });
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
function assertNoDestinationDrift(preview) {
  for (const expected of preview.operations) {
    const exists = fs.existsSync(expected.destinationPath);
    // re-run the same classification logic preflight used; if it differs, apply will throw [294]
    if (expected.classification === 'create' && exists) {
      throw new Error(`Destination ${expected.destinationPath} appeared after preflight; re-preflight before apply.`);
    }
    if ((expected.classification === 'json-merge' || expected.classification === 'managed-json-update' || expected.classification === 'managed-update') && !exists) {
      throw new Error(`Destination ${expected.destinationPath} disappeared after preflight; re-preflight before apply.`);
    }
  }
}
assertNoDestinationDrift(preview.harnesses.find(h=>h.id==='kimi').preview);

Type guard

null

Try / catch

try {
  await applyMultiHarnessPlan(preview);
} catch (err) {
  if (/destination changed after Kimi preflight/.test(err.message)) {
    // re-run preflight against current fs, then apply immediately
    const kimi = preview.harnesses.find(h => h.id === 'kimi');
    kimi.preview = preflightManagedPlan(kimi.preview.plan);
    await applyMultiHarnessPlan(preview);
  } else throw err;
}

Prevention

When it happens

Trigger: Fires when expected is undefined, expected.kind !== operation.kind, the canonical destinations differ, or expected.classification !== currentClassification. Occurs when a file was created/deleted/edited between preflightManaged and applyInstallPlan, when operations are reordered or dropped, when ownership changed (e.g., a file became owned or stopped being owned), or when the plan object was mutated between preflight and apply.

Common situations: User creates/removes a destination file during the preview->apply window; another ECC process writes files in the same project; the plan array was filtered or reordered before apply; the install-state changed classification (e.g., a file went from absent 'create' to present 'json-merge'); sync tools modifying files mid-install.

Related errors


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