affaan-m/ECC · warning · Error

Refusing to replace unowned existing file: ${destinationPath

Error message

Refusing to replace unowned existing file: ${destinationPath}

What it means

Thrown by classifyManagedOperation when a destination file already exists, is not in ownedDestinations, is not a merge-json operation, and is not byte-identical to the source of a copy-file operation. ECC will not replace a pre-existing file it cannot prove it owns and cannot prove is identical to what it would write. This is the catch-all ownership guard for non-merge writes, preventing the installer from clobbering user files.

Source

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

    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'
    && fs.existsSync(operation.sourcePath)
    && fs.statSync(destinationPath).isFile()
    && fs.readFileSync(operation.sourcePath).equals(fs.readFileSync(destinationPath))
  ) {
    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 01e15490f0)

Solutions

  1. If you intentionally keep the file, move it aside or remove the operation from the managed profile so ECC does not target that path.
  2. If ECC should own it, delete plan.installStatePath is NOT enough — you must either delete the destination file (so it classifies as 'create') or let ECC take ownership by establishing the state (e.g., copy the exact ECC source bytes in first so it classifies 'identical').
  3. Verify the sourcePath bytes: if you expect 'identical' classification, ensure operation.sourcePath exists and matches the destination exactly (EOL, encoding).
  4. Back up the existing file before deciding; ECC will not overwrite it without proof.

Example fix

// before: user has /home/me/app/.claude/rules/foo.md; ECC plan copies its own foo.md there
classifyManagedOperation(op, owned); // throws [292]

// after (option A): let ECC create it fresh
fs.rmSync('/home/me/app/.claude/rules/foo.md'); // back up first
classifyManagedOperation(op, owned); // -> 'create'
// after (option B): make bytes identical to ECC source so it classifies 'identical'
fs.copyFileSync(op.sourcePath, '/home/me/app/.claude/rules/foo.md');
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs'); const crypto = require('crypto');
function assertSafeReplace(destinationPath, operation, ownedDestinations) {
  if (!fs.existsSync(destinationPath)) return; // 'create' is fine
  if (ownedDestinations.has(destinationPath)) return; // 'managed-update' is fine
  if (operation.kind === 'copy-file' && operation.sourcePath && fs.existsSync(operation.sourcePath)
      && fs.statSync(destinationPath).isFile()
      && fs.readFileSync(operation.sourcePath).equals(fs.readFileSync(destinationPath))) return; // 'identical'
  throw new Error(`${destinationPath} exists, is unowned, and differs from source; back up and remove it or let ECC own it.`);
}
assertSafeReplace(operation.destinationPath, operation, ownedDestinations);

Type guard

null

Try / catch

try {
  classifyManagedOperation(operation, ownedDestinations);
} catch (err) {
  if (/Refusing to replace unowned existing file/.test(err.message)) {
    // back up the user file, remove it so install classifies 'create', then retry
    fs.renameSync(operation.destinationPath, operation.destinationPath + '.bak');
    classifyManagedOperation(operation, ownedDestinations);
  } else throw err;
}

Prevention

When it happens

Trigger: Fires at the end of classifyManagedOperation after the create/merge/managed-update/identical branches all fail. Reached when fs.existsSync(destinationPath) is true, ownedDestinations lacks the canonical destination, operation.kind is not 'merge-json', and either operation.kind is not 'copy-file', sourcePath is missing, the destination is not a regular file, or the bytes differ from the source.

Common situations: A user-created file lives at a path ECC's catalog now targets; a managed file lost its install-state ownership record (state deleted/corrupt); the source file changed so a previously-identical file is now different; the destination is a directory where a file is expected; running install into a project that already has same-named files from another source.

Related errors


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