{"record":{"id":"79422f93b2aa5a6a","repo":"affaan-m/ECC","slug":"refusing-to-overwrite-unowned-json-fields-at-des","errorCode":null,"errorMessage":"Refusing to overwrite unowned JSON fields at ${destinationPath}: ${conflicts.join(', ')}","messagePattern":"Refusing to overwrite unowned JSON fields at (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"scripts/lib/multi-harness-setup.js","lineNumber":228,"sourceCode":"    const currentValue = current[key];\n    const field = prefix ? `${prefix}.${key}` : key;\n    if (isPlainObject(currentValue) && isPlainObject(patchValue)) {\n      return findJsonConflicts(currentValue, patchValue, field);\n    }\n    return JSON.stringify(currentValue) === JSON.stringify(patchValue) ? [] : [field];\n  });\n}\n\nfunction classifyManagedOperation(operation, ownedDestinations) {\n  const destinationPath = operation.destinationPath;\n  if (!fs.existsSync(destinationPath)) return 'create';\n  const canonicalDestination = canonicalPath(destinationPath);\n  if (operation.kind === 'merge-json') {\n    const current = assertMergeDestination(destinationPath);\n    if (ownedDestinations.has(canonicalDestination)) return 'managed-json-update';\n    const conflicts = findJsonConflicts(current, operation.mergePayload);\n    if (conflicts.length > 0) {\n      throw new Error(\n        `Refusing to overwrite unowned JSON fields at ${destinationPath}: ${conflicts.join(', ')}`\n      );\n    }\n    return 'json-merge';\n  }\n  if (ownedDestinations.has(canonicalDestination)) return 'managed-update';\n  if (\n    operation.kind === 'copy-file'\n    && typeof operation.sourcePath === 'string'\n    && fs.existsSync(operation.sourcePath)\n    && fs.statSync(destinationPath).isFile()\n    && fs.readFileSync(operation.sourcePath).equals(fs.readFileSync(destinationPath))\n  ) {\n    return 'identical';\n  }\n  throw new Error(`Refusing to replace unowned existing file: ${destinationPath}`);\n}\n","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/multi-harness-setup.js#L210-L246","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: user has settings.json { \"hooks\": { \"PreToolUse\": \"mine\" } }\n// ECC merge-json patch: { \"hooks\": { \"PreToolUse\": \"ecc-hook\" } }\nclassifyManagedOperation(op, owned); // throws [291]: hooks.PreToolUse\n\n// after (option A): align your value to ECC's\n// settings.json: { \"hooks\": { \"PreToolUse\": \"ecc-hook\" } }\n// after (option B): let ECC own the file by installing once first so it is in ownedDestinations","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nfunction isPlainObject(v){return Boolean(v)&&typeof v==='object'&&!Array.isArray(v);}\nfunction findConflicts(current, patch, prefix='') {\n  if (!isPlainObject(patch)) return [];\n  return Object.entries(patch).flatMap(([k, pv]) => {\n    if (!Object.prototype.hasOwnProperty.call(current, k)) return [];\n    const cv = current[k];\n    const f = prefix ? `${prefix}.${k}` : k;\n    if (isPlainObject(cv) && isPlainObject(pv)) return findConflicts(cv, pv, f);\n    return JSON.stringify(cv) === JSON.stringify(pv) ? [] : [f];\n  });\n}\nfunction assertNoUnownedConflicts(destinationPath, mergePayload) {\n  if (!fs.existsSync(destinationPath)) return;\n  const conflicts = findConflicts(JSON.parse(fs.readFileSync(destinationPath,'utf8')), mergePayload);\n  if (conflicts.length) throw new Error(`Conflicts at ${destinationPath}: ${conflicts.join(', ')}`);\n}\nassertNoUnownedConflicts(operation.destinationPath, operation.mergePayload);","typeGuard":"null","tryCatchPattern":"try {\n  classifyManagedOperation(operation, ownedDestinations);\n} catch (err) {\n  if (/Refusing to overwrite unowned JSON fields/.test(err.message)) {\n    // surface conflicting paths so the user can reconcile, or mark the file as managed first\n    throw err;\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["merge-json","conflict-detection","ownership","configuration","kimi"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}