affaan-m/ECC · error · Error
Missing merge payload for ${operation.destinationPath}
Error message
Missing merge payload for ${operation.destinationPath} What it means
Thrown inside applyInstallPlan when an operation with kind === 'merge-json' has operation.mergePayload === undefined after cloneJsonValue. The applier deep-merges mergePayload into the destination file; an undefined payload means there is nothing to merge and the installer refuses rather than writing an empty object and destroying the existing destination. The Kimi adapter populates mergePayload when constructing merge operations; other/custom adapters must do the same.
Source
Thrown at scripts/lib/install/apply.js:317
}
for (const operation of appliedPlan.operations) {
assertSafeInstallOperation(appliedPlan, operation);
assertSafeClaudeSkillOperation(appliedPlan, operation);
fs.mkdirSync(path.dirname(operation.destinationPath), { recursive: true });
// Recheck directories that were absent during the first validation. This
// narrows the symlink-swap window around mkdirSync, but path checks cannot
// eliminate a later TOCTOU race before the file write.
assertSafeInstallOperation(appliedPlan, operation);
assertSafeClaudeSkillOperation(appliedPlan, operation);
if (typeof beforeOperationWrite === 'function') {
beforeOperationWrite({ plan: appliedPlan, operation });
}
if (operation.kind === 'merge-json') {
const payload = cloneJsonValue(operation.mergePayload);
if (payload === undefined) {
throw new Error(`Missing merge payload for ${operation.destinationPath}`);
}
const filteredPayload = (
isMcpConfigPath(operation.destinationPath) && disabledServers.length > 0
)
? filterMcpConfig(payload, disabledServers).config
: payload;
const currentValue = fs.existsSync(operation.destinationPath)
? readJsonObject(operation.destinationPath, 'existing JSON config')
: {};
const mergedValue = deepMergeJson(currentValue, filteredPayload);
fs.writeFileSync(operation.destinationPath, formatJson(mergedValue), 'utf8');
continue;
}
if (operation.kind === 'copy-file' && isMcpConfigPath(operation.destinationPath) && disabledServers.length > 0) {
const sourceConfig = readJsonObject(operation.sourcePath, 'MCP config');View on GitHub (pinned to 01e15490f0)
Solutions
- Confirm every merge-json operation in plan.operations has a non-undefined mergePayload.
- Build operations via createManagedOperation({ kind: 'merge-json', mergePayload: {...}, ... }) so the field is always populated.
- Do not serialize a plan to JSON and reload it — JSON drops undefined values; pass the in-memory object.
- If regenerating from a stale source, re-run the planning step rather than reusing the old plan.
Example fix
// before
createManagedOperation({
kind: 'merge-json',
moduleId, sourceRelativePath,
destinationPath,
strategy: 'merge-json',
});
// after
createManagedOperation({
kind: 'merge-json',
moduleId, sourceRelativePath,
destinationPath,
strategy: 'merge-json',
mergePayload: readJsonObject(sourcePath, sourceRelativePath),
}); Defensive patterns
Strategy: validation
Validate before calling
function assertMergePayloadsPresent(operations) {
for (const op of operations) {
if (op.kind === 'merge-json' && op.mergePayload === undefined) {
throw new Error(`merge-json operation missing mergePayload: ${op.destinationPath}`);
}
}
}
assertMergePayloadsPresent(plan.operations); Type guard
function isCompleteMergeOperation(op) {
return Boolean(
op && op.kind === 'merge-json' && op.mergePayload !== undefined
);
} Prevention
- Always pair kind: 'merge-json' with a mergePayload value when calling createManagedOperation.
- Never JSON-round-trip install plans — undefined fields are lost; pass the in-memory object.
- Regenerate plans from source after an ECC version bump rather than reusing stale plans.
When it happens
Trigger: A custom adapter creates a merge-json operation without setting mergePayload; a plan that was serialized to JSON and back (undefined fields are dropped); a stale plan from an older ECC version that named the field differently.
Common situations: Forking the kimi-project adapter pattern and forgetting the mergePayload: readJsonObject(...) line; JSON-round-tripping a plan; building operations by hand without createManagedOperation.
Related errors
- Missing merge payload for repair: ${operation.destinationPat
- Missing merge payload for uninstall: ${operation.destination
- Cannot merge ECC configuration into invalid JSON at ${destin
- Cannot merge ECC configuration at ${destinationPath}: expect
- ECC_PROJECT_DIR must be a child path within /workspace.
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/d43a6e0ec37a0937.
Report an issue: GitHub.