affaan-m/ECC · warning · Error
Refusing to overwrite unowned JSON fields at ${destinationPa
Error message
Refusing to overwrite unowned JSON fields at ${destinationPath}: ${conflicts.join(', ')} What it means
Thrown by classifyManagedOperation for a merge-json operation on an unowned destination when findJsonConflicts detects at least one field where the patch wants to set a value that differs from the existing value. ECC will only add fields that are absent (or overwrite fields it owns); overwriting fields the user set themselves is destructive, so the conflicting field paths are listed and the operation aborts. Nested objects are recursed; matching values are not conflicts.
Source
Thrown at scripts/lib/multi-harness-setup.js:228
const currentValue = current[key];
const field = prefix ? `${prefix}.${key}` : key;
if (isPlainObject(currentValue) && isPlainObject(patchValue)) {
return findJsonConflicts(currentValue, patchValue, field);
}
return JSON.stringify(currentValue) === JSON.stringify(patchValue) ? [] : [field];
});
}
function classifyManagedOperation(operation, ownedDestinations) {
const destinationPath = operation.destinationPath;
if (!fs.existsSync(destinationPath)) return 'create';
const canonicalDestination = canonicalPath(destinationPath);
if (operation.kind === 'merge-json') {
const current = assertMergeDestination(destinationPath);
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'
&& 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}`);
}
View on GitHub (pinned to 01e15490f0)
Solutions
- Read the conflicting paths in the message and reconcile: change your config to match ECC's value, or change ECC's mergePayload (profile) to match yours.
- If you want ECC to take ownership of that file, mark the destination as managed first (run a prior install that records ownership), then the merge becomes a managed-json-update.
- Remove the conflicting key from your config so ECC adds it fresh.
- Choose a different profile whose patch does not touch your customized keys.
Example fix
// before: user has settings.json { "hooks": { "PreToolUse": "mine" } }
// ECC merge-json patch: { "hooks": { "PreToolUse": "ecc-hook" } }
classifyManagedOperation(op, owned); // throws [291]: hooks.PreToolUse
// after (option A): align your value to ECC's
// settings.json: { "hooks": { "PreToolUse": "ecc-hook" } }
// after (option B): let ECC own the file by installing once first so it is in ownedDestinations Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs');
function isPlainObject(v){return Boolean(v)&&typeof v==='object'&&!Array.isArray(v);}
function findConflicts(current, patch, prefix='') {
if (!isPlainObject(patch)) return [];
return Object.entries(patch).flatMap(([k, pv]) => {
if (!Object.prototype.hasOwnProperty.call(current, k)) return [];
const cv = current[k];
const f = prefix ? `${prefix}.${k}` : k;
if (isPlainObject(cv) && isPlainObject(pv)) return findConflicts(cv, pv, f);
return JSON.stringify(cv) === JSON.stringify(pv) ? [] : [f];
});
}
function assertNoUnownedConflicts(destinationPath, mergePayload) {
if (!fs.existsSync(destinationPath)) return;
const conflicts = findConflicts(JSON.parse(fs.readFileSync(destinationPath,'utf8')), mergePayload);
if (conflicts.length) throw new Error(`Conflicts at ${destinationPath}: ${conflicts.join(', ')}`);
}
assertNoUnownedConflicts(operation.destinationPath, operation.mergePayload); Type guard
null
Try / catch
try {
classifyManagedOperation(operation, ownedDestinations);
} catch (err) {
if (/Refusing to overwrite unowned JSON fields/.test(err.message)) {
// surface conflicting paths so the user can reconcile, or mark the file as managed first
throw err;
}
throw err;
} Prevention
- Reconcile conflicting keys before install: align your value with ECC's or vice-versa.
- Remove keys from your config that you want ECC to manage, so they are added fresh.
- Run a prior install to establish ownership if you want ECC to take over the whole file.
- Pick a profile whose merge payload does not collide with your customizations.
When it happens
Trigger: Fires when ownedDestinations does not contain the canonical destination and findJsonConflicts(current, operation.mergePayload) returns a non-empty list. Each entry is a dotted path where current[path] exists, both sides are non-objects (or type-mismatched), and JSON.stringify(currentValue) !== JSON.stringify(patchValue). Typical when the user already configured a setting ECC's patch also tries to set with a different value.
Common situations: User set 'hooks' or 'permissions' in settings.json with their own value; an existing tsconfig compilerOptions value differs from ECC's recommendation; two ECC modules try to write the same key differently; profile merge payload changed a default the user customized.
Related errors
- Cannot merge ECC configuration into invalid JSON at ${destin
- Cannot merge ECC configuration at ${destinationPath}: expect
- Invalid Kimi install profile: ${profile}
- 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/79422f93b2aa5a6a.
Report an issue: GitHub.