affaan-m/ECC · error · Error

Refusing to replace unowned existing file

Error message

Refusing to replace unowned existing file: ${destinationPath}

What it means

classifyManagedOperation throws this when the destination file already exists, is not an ECC-owned destination, and is not byte-identical to the operation's copy-file source. ECC will not replace a file it does not own with different content, since that could destroy user work. Only 'create' (missing file), 'managed-update' (owned), 'identical' (same bytes as source), and safe merges are permitted.

Solutions

  1. Diff the existing file against the ECC source; if the existing content is disposable, delete or move it and re-run the install so ECC creates it fresh.
  2. Merge your customizations into the ECC version of the file, then re-run so the content is identical or properly owned via a fresh install-state run.
  3. Restore or regenerate the install-state (guided install) so previously installed files are recognized as owned before updating.
  4. Keep project-specific files at paths ECC does not manage instead of editing managed files in place.

Example fix

// before: repo's own AGENTS.md blocks install
mv AGENTS.md AGENTS.project.md && ecc-install   // install creates ECC AGENTS.md
// after: fold project notes into the managed file, or keep a separate file ECC never writes
Defensive patterns

Strategy: try-catch

Validate before calling

if (fs.existsSync(destinationPath)
  && !ownedDestinations.has(path.resolve(destinationPath))
  && !fs.readFileSync(destinationPath).equals(fs.readFileSync(operation.sourcePath))) {
  console.warn(`${destinationPath} exists and differs from ECC source; move or merge it before installing.`);
}

Try / catch

try {
  await runInstall(plan);
} catch (err) {
  if (String(err.message).startsWith('Refusing to replace unowned existing file:')) {
    const file = err.message.replace('Refusing to replace unowned existing file: ', '');
    console.error(`Back up or merge ${file}, then re-run the install.`);
  } else throw err;
}

Prevention

When it happens

Trigger: Installing a copy-file operation where the destination file exists, ownedDestinations does not contain its canonical path, and destination content differs from operation.sourcePath content — e.g. installing into a repo that already has its own AGENTS.md/hooks/commands files.

Common situations: Project already defines its own AGENTS.md, hooks.json, or command files before installing ECC; re-installing after install-state was lost/deleted so previous files are no longer 'owned'; installing a newer ECC over files customized locally.

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


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

Appendix: source

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

    const current = assertMergeDestination(destinationPath, destination);
    if (ownedDestinations.has(canonicalDestination)) return 'managed-json-update';
    const conflicts = findJsonConflicts(current, operation.mergePayload);
    if (conflicts.length > 0) {
      throw new Error(
        `Refusing to overwrite unowned JSON fields at ${destinationPath}: ${conflicts.join(', ')}`
      );
    }
    return 'json-merge';
  }
  if (ownedDestinations.has(canonicalDestination)) return 'managed-update';
  if (
    operation.kind === 'copy-file'
    && typeof operation.sourcePath === 'string'
    && readRegularFileSnapshot(operation.sourcePath)?.content.equals(destination.content)
  ) {
    return 'identical';
  }
  throw new Error(`Refusing to replace unowned existing file: ${destinationPath}`);
}

function writableRequirement(destinationPath) {
  if (fs.existsSync(destinationPath)) {
    const mode = fs.statSync(destinationPath).isDirectory()
      ? fs.constants.W_OK | fs.constants.X_OK
      : fs.constants.W_OK;
    return { candidatePath: destinationPath, mode };
  }

  let candidatePath = path.dirname(destinationPath);
  while (!fs.existsSync(candidatePath)) {
    const parentPath = path.dirname(candidatePath);
    if (parentPath === candidatePath) break;
    candidatePath = parentPath;
  }
  return {
    candidatePath,

View on GitHub (pinned to 8321021c54)