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
- Re-run preflightManaged immediately before apply so the preview reflects the current filesystem, then apply without delay.
- Do not mutate plan.operations or plan.harnesses between preview and apply.
- Stop concurrent processes that touch destination paths during install.
- 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
- Re-run preflightManagedPlan immediately before apply when any time has passed.
- Do not create/delete/edit destination files between preflight and apply.
- Do not reorder or filter plan.operations between preview and apply.
- Stop other processes that write into the project during install.
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
- ${label} destination is not writable by the current user: ${
- Refusing to overwrite an unowned or changed install-state at
- Refusing to trust managed install-state at ${plan.installSta
- Refusing to trust managed install-state at ${plan.installSta
- 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/26f7fe4a3dcccc43.
Report an issue: GitHub.