affaan-m/ECC · error
Refusing install: a user-owned file appeared at
Error message
Refusing install: a user-owned file appeared at ${operation.destinationPath} after planning. Rerun the installer to preserve it. What it means
assertNoNewUserOwnedFile is a last-line defense run while applying the install plan (under lock). For copy-file operations whose destination was NOT already a managed destination, it checks whether a file now exists at the destination that did not exist at planning time. If so, it assumes the user placed a file there and refuses the copy so the user's file is preserved.
Solutions
- Rerun the installer as the message says — the new planning pass will detect the existing file and treat it as user-owned (skip/preserve).
- If the file is not yours and should be overwritten, delete it or mark it as managed, then reinstall.
- Ensure no other process writes to the install directory while the installer runs.
- Verify the destination isn't being recreated by a git operation or postinstall script between plan and apply.
Example fix
// before: file appeared after planning → install aborts applyInstallPlan(plan); // after: re-plan so the existing user file is detected and preserved const freshPlan = buildInstallPlan(options); // planning now sees the user file applyInstallPlan(freshPlan);
Defensive patterns
Strategy: validation
Validate before calling
if (fs.existsSync(dest) && !plan.managedDestinations.has(norm(dest))) {
console.warn(`user file exists at ${dest}; rerun installer to re-plan before applying`);
} Type guard
function destinationIsSafeToCopy(op, managedDestinations) {
return op.kind !== 'copy-file'
|| managedDestinations.has(norm(op.destinationPath))
|| !fs.existsSync(op.destinationPath);
} Try / catch
try {
applyInstallPlanLocked(plan);
} catch (e) {
if (e.message.includes('user-owned file appeared')) {
const fresh = buildInstallPlan(plan.options); // re-plan sees the new user file
return applyInstallPlanLocked(fresh);
}
throw e;
} Prevention
- Don't write into the install directory while the installer runs.
- Rerun planning immediately before applying; don't reuse old plans.
- Commit or stash local changes before scripted installs.
- Disable postinstall/git hooks that create files in managed destinations mid-install.
When it happens
Trigger: Calling applyInstallPlanLocked → assertNoNewUserOwnedFile when: (1) pathExists(operation.destinationPath) is true at apply time but the destination was absent (and unmanaged) during planning; (2) something created a file at that path between plan and apply — another process, a git checkout, or a hook.
Common situations: User creates a config file while the installer is running; a package manager or scaffolding tool writes the destination concurrently; rerunning an install after a partial failure where a previously-unmanaged file appeared; copying a repo that now contains the destination file.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- Another ECC process is updating Claude settings
- Another Nasiko lifecycle operation won lock acquisition
- Another Nasiko lifecycle operation won stale-lock recovery
- artifact byte count exceeded during reading
- artifact changed before reading
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/e21f694d4ad54061.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install/ownership-guard.js:134
...skippedOperations,
],
warnings: [...((migration && migration.warnings) || []), ...warnings],
bridgeState,
finalState,
// Only keep bridge persistence when operations actually remain; a fully
// skipped plan installs nothing and must not claim anything.
requiresBridgeState: Boolean(migration.requiresBridgeState)
&& appliedOperations.length > 0,
};
}
function assertNoNewUserOwnedFile(migration, operation) {
if (operation.kind !== 'copy-file'
|| migration.managedDestinations.has(comparablePath(operation.destinationPath))
|| !pathExists(operation.destinationPath)) {
return;
}
throw new Error(`Refusing install: a user-owned file appeared at ${operation.destinationPath} after planning. Rerun the installer to preserve it.`);
}
function preserveUnwrittenFiles(state, migration, writtenDestinations) {
const writtenPaths = new Set([...writtenDestinations].map(comparablePath));
return {
...state,
operations: state.operations.filter(operation => (
operation.kind !== 'copy-file'
|| migration.managedDestinations.has(comparablePath(operation.destinationPath))
|| writtenPaths.has(comparablePath(operation.destinationPath))
|| !pathExists(operation.destinationPath)
)).map(operation => {
const destination = comparablePath(operation.destinationPath);
return operation.kind === 'copy-file' && !writtenPaths.has(destination)
? migration.previousManagedOperations.get(destination) || operation
: operation;
}),
};View on GitHub (pinned to 8321021c54)