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
- Wrap the file's content in a top-level object (e.g. { "items": [...] }) and re-run the install.
- Delete the file so the installer creates a fresh JSON object config, then port over any array/string data under a named key.
- 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
- Keep merge-json destinations as top-level objects; nest arrays under named keys.
- Never write null/arrays/strings as entire config file contents.
- Add a pre-install check asserting config shapes.
- Document config file shape for the team to prevent accidental rewrites.
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
- candidate configuration exceeds 1 MiB
- Cannot merge ECC configuration into invalid JSON at
- Failed to load policy from
- Failed to parse
- input_artifacts must be a list of local paths
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)