{"record":{"id":"d43a6e0ec37a0937","repo":"affaan-m/ECC","slug":"missing-merge-payload-for-operation-destinationp","errorCode":null,"errorMessage":"Missing merge payload for ${operation.destinationPath}","messagePattern":"Missing merge payload for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/install/apply.js","lineNumber":317,"sourceCode":"  }\n\n  for (const operation of appliedPlan.operations) {\n    assertSafeInstallOperation(appliedPlan, operation);\n    assertSafeClaudeSkillOperation(appliedPlan, operation);\n    fs.mkdirSync(path.dirname(operation.destinationPath), { recursive: true });\n    // Recheck directories that were absent during the first validation. This\n    // narrows the symlink-swap window around mkdirSync, but path checks cannot\n    // eliminate a later TOCTOU race before the file write.\n    assertSafeInstallOperation(appliedPlan, operation);\n    assertSafeClaudeSkillOperation(appliedPlan, operation);\n    if (typeof beforeOperationWrite === 'function') {\n      beforeOperationWrite({ plan: appliedPlan, operation });\n    }\n\n    if (operation.kind === 'merge-json') {\n      const payload = cloneJsonValue(operation.mergePayload);\n      if (payload === undefined) {\n        throw new Error(`Missing merge payload for ${operation.destinationPath}`);\n      }\n\n      const filteredPayload = (\n        isMcpConfigPath(operation.destinationPath) && disabledServers.length > 0\n      )\n        ? filterMcpConfig(payload, disabledServers).config\n        : payload;\n\n      const currentValue = fs.existsSync(operation.destinationPath)\n        ? readJsonObject(operation.destinationPath, 'existing JSON config')\n        : {};\n      const mergedValue = deepMergeJson(currentValue, filteredPayload);\n      fs.writeFileSync(operation.destinationPath, formatJson(mergedValue), 'utf8');\n      continue;\n    }\n\n    if (operation.kind === 'copy-file' && isMcpConfigPath(operation.destinationPath) && disabledServers.length > 0) {\n      const sourceConfig = readJsonObject(operation.sourcePath, 'MCP config');","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/install/apply.js#L299-L335","documentation":"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.","triggerScenarios":"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.","commonSituations":"Forking the kimi-project adapter pattern and forgetting the mergePayload: readJsonObject(...) line; JSON-round-tripping a plan; building operations by hand without createManagedOperation.","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."],"exampleFix":"// before\ncreateManagedOperation({\n  kind: 'merge-json',\n  moduleId, sourceRelativePath,\n  destinationPath,\n  strategy: 'merge-json',\n});\n\n// after\ncreateManagedOperation({\n  kind: 'merge-json',\n  moduleId, sourceRelativePath,\n  destinationPath,\n  strategy: 'merge-json',\n  mergePayload: readJsonObject(sourcePath, sourceRelativePath),\n});","handlingStrategy":"validation","validationCode":"function assertMergePayloadsPresent(operations) {\n  for (const op of operations) {\n    if (op.kind === 'merge-json' && op.mergePayload === undefined) {\n      throw new Error(`merge-json operation missing mergePayload: ${op.destinationPath}`);\n    }\n  }\n}\nassertMergePayloadsPresent(plan.operations);","typeGuard":"function isCompleteMergeOperation(op) {\n  return Boolean(\n    op && op.kind === 'merge-json' && op.mergePayload !== undefined\n  );\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["install-apply","merge-json","validation"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}