affaan-m/ECC · error · Error

Cannot merge ECC configuration into invalid JSON at

Error message

Cannot merge ECC configuration into invalid JSON at ${destinationPath}: ${error.message}

What it means

assertMergeDestination parses an existing merge-json destination file as JSON before ECC merges its configuration into it. If JSON.parse throws, the error is rethrown with this message naming the destination and the underlying parse error. ECC refuses to merge into malformed JSON because the merge would either fail or silently destroy the invalid content.

Solutions

  1. Open the destination file, fix the JSON syntax error reported in error.message (line/column), then re-run the install.
  2. If the file's content is disposable, delete it so the installer recreates it, then reapply intended settings as valid JSON.
  3. Convert comments to valid JSON (or move the file to a JSONC path the tool actually supports) before installing.

Example fix

// before: agents.json
{ "name": "x", } // trailing comma
// after
{ "name": "x" }
Defensive patterns

Strategy: validation

Validate before calling

function assertValidJsonObjectFile(p) {
  if (!fs.existsSync(p)) return;
  const parsed = JSON.parse(fs.readFileSync(p, 'utf8')); // throws with position info
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error(`${p} must contain a JSON object`);
  }
}
assertValidJsonObjectFile(destinationPath);

Type guard

function isJsonObjectFile(p) {
  try {
    const v = JSON.parse(fs.readFileSync(p, 'utf8'));
    return Boolean(v) && typeof v === 'object' && !Array.isArray(v);
  } catch { return false; }
}

Try / catch

try {
  await runInstall(plan);
} catch (err) {
  if (String(err.message).startsWith('Cannot merge ECC configuration into invalid JSON')) {
    const file = err.message.match(/at (.+?):/)[1];
    console.error(`Fix JSON syntax in ${file} or delete it and re-run.`);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling an install/plan operation with kind 'merge-json' (via classifyManagedOperation → assertMergeDestination) where the destination file exists but contains syntactically invalid JSON — trailing commas, comments, truncated writes, or a non-JSON file placed at the config path.

Common situations: A user hand-edited a harness config (e.g. agents/openai.yaml-style JSON or .mcp.json) and introduced a syntax error; a previous crashed write left a truncated file; someone renamed a JSONC file to .json keeping comments.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


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

Appendix: source

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

    ) {
      throw new Error(
        `Refusing unverified ownership from install-state at ${plan.installStatePath}: `
        + `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)) {

View on GitHub (pinned to 8321021c54)