affaan-m/ECC · error · Error

Cannot merge ECC configuration at

Error message

Cannot merge ECC configuration at ${destinationPath}: expected a JSON object.

What it means

After successfully parsing, assertMergeDestination requires the parsed value to be a non-null, non-array object, because ECC merges key/value configuration into it. This error is thrown when the destination JSON parses to null, an array, a string, a number, or a boolean instead of an object. Merging into such values is not defined, so the operation is rejected.

Solutions

  1. Wrap the file's content in a top-level object (e.g. { "items": [...] }) and re-run the install.
  2. Delete the file so the installer creates a fresh JSON object config, then port over any array/string data under a named key.
  3. Verify the destination path is the config file ECC expects, not a data file that legitimately holds a non-object.

Example fix

// before: config.json
["agent-a", "agent-b"]
// after
{ "agents": ["agent-a", "agent-b"] }
Defensive patterns

Strategy: type-guard

Validate before calling

if (fs.existsSync(destinationPath)) {
  const v = JSON.parse(fs.readFileSync(destinationPath, 'utf8'));
  if (!v || typeof v !== 'object' || Array.isArray(v)) {
    throw new Error(`${destinationPath} must be a JSON object before install`);
  }
}

Type guard

function isJsonObject(v) {
  return Boolean(v) && typeof v === 'object' && !Array.isArray(v);
}
// guard: isJsonObject(JSON.parse(fs.readFileSync(destinationPath, 'utf8')))

Try / catch

try {
  await runInstall(plan);
} catch (err) {
  if (String(err.message).includes('expected a JSON object')) {
    const file = err.message.match(/at (.+?):/)[1];
    fs.writeFileSync(file, '{}'); // or fix the shape manually, then re-run
  } else throw err;
}

Prevention

When it happens

Trigger: A merge-json destination file contains valid JSON but not an object — e.g. the file holds '[ ]', '"text"', 'null', or a top-level number — reached via classifyManagedOperation during install planning.

Common situations: A user replaced config content with a JSON array of items; a script wrote 'null' to reset a config; config file accidentally contains just a JSON string; empty-array placeholder written by another tool.

Related errors


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

Appendix: source

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

        + `content digest does not match ${destinationPath}.`
      );
    }
    destinations.add(canonicalDestination);
  }
  return { destinations, stateFingerprint: validatedFingerprint };
}

function assertMergeDestination(destinationPath, existingSnapshot = null) {
  const snapshot = existingSnapshot || readRegularFileSnapshot(destinationPath);
  if (!snapshot) return null;
  let current;
  try {
    current = JSON.parse(snapshot.content.toString('utf8'));
  } catch (error) {
    throw new Error(`Cannot merge ECC configuration into invalid JSON at ${destinationPath}: ${error.message}`);
  }
  if (!current || typeof current !== 'object' || Array.isArray(current)) {
    throw new Error(`Cannot merge ECC configuration at ${destinationPath}: expected a JSON object.`);
  }
  return current;
}

function isPlainObject(value) {
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}

function findJsonConflicts(current, patch, prefix = '') {
  if (!isPlainObject(patch)) return [];
  return Object.entries(patch).flatMap(([key, patchValue]) => {
    if (!Object.prototype.hasOwnProperty.call(current, key)) return [];
    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];

View on GitHub (pinned to 8321021c54)