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

  1. Confirm every merge-json operation in plan.operations has a non-undefined mergePayload.
  2. Build operations via createManagedOperation({ kind: 'merge-json', mergePayload: {...}, ... }) so the field is always populated.
  3. Do not serialize a plan to JSON and reload it — JSON drops undefined values; pass the in-memory object.
  4. 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

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


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/d43a6e0ec37a0937. Report an issue: GitHub.