{"record":{"id":"62b5607352f38e91","repo":"apache/beam","slug":"expected-distinct-components-id","errorCode":null,"errorMessage":"Expected distinct components: ${id}","messagePattern":"Expected distinct components: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"sdks/typescript/src/apache_beam/testing/multi_pipeline_runner.ts","lineNumber":144,"sourceCode":"      return \"namespace_\" + this.counter + \"_\";\n    } finally {\n      this.counter += 1;\n    }\n  }\n\n  mergePipeline(pipeline: runnerApi.Pipeline) {\n    if (this.allPipelines === undefined) {\n      this.allPipelines = runnerApi.Pipeline.create({\n        components: runnerApi.Components.create({}),\n      });\n    }\n    function mergeComponents(src, dest) {\n      for (const [id, proto] of Object.entries(src)) {\n        if (dest[id] === undefined) {\n          dest[id] = proto;\n        } else if (dest[id] != proto) {\n          require(\"assert\").deepEqual(dest[id], proto);\n          throw new Error(\"Expected distinct components: \" + id);\n        }\n      }\n    }\n    mergeComponents(\n      pipeline.components?.transforms,\n      this.allPipelines.components?.transforms,\n    );\n    mergeComponents(\n      pipeline.components?.pcollections,\n      this.allPipelines.components?.pcollections,\n    );\n    mergeComponents(\n      pipeline.components?.coders,\n      this.allPipelines.components?.coders,\n    );\n    mergeComponents(\n      pipeline.components?.windowingStrategies,\n      this.allPipelines.components?.windowingStrategies,","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/typescript/src/apache_beam/testing/multi_pipeline_runner.ts#L126-L162","documentation":"mergeComponents() merges per-pipeline component maps (transforms, pcollections, etc.) into the combined allPipelines proto. If an id already exists in the destination with a different proto, it throws 'Expected distinct components: <id>' to prevent silently overwriting or aliasing incompatible components across pipelines.","triggerScenarios":"Merging two or more pipelines into MultiPipelineRunner where two pipelines define the same component id (transform/pcollection/coder name) with different protos.","commonSituations":"Two pipelines generated by code that reuses default transform names (e.g. both contain a transform with the same auto-generated id) while being merged in a multi-pipeline test run.","solutions":["Rename transforms with unique labels in each pipeline (apply(PTransform, ...).apply with distinct names) so component ids differ.","Investigate the duplicate component id and why its protos differ between pipelines.","Remove the deepEqual assert call if near-equal protos should be tolerated, or normalize ids before merging."],"exampleFix":"// before\nconst p1 = pipeline.apply(\"Sum\", sum); // in two pipelines, same default ids\n// after\npipeline1.apply(\"Sum-A\", sum); pipeline2.apply(\"Sum-B\", sum);","handlingStrategy":"validation","validationCode":"function findDuplicateIds(pipelines) {\n  const seen = new Map(); const dupes = [];\n  for (const p of pipelines) for (const id of Object.keys(p.components.transforms)) {\n    if (seen.has(id)) dupes.push(id); else seen.set(id, true);\n  }\n  return dupes;\n}","typeGuard":"null","tryCatchPattern":"try {\n  await multiRunner.runAsync();\n} catch (e) {\n  if (e.message.startsWith(\"Expected distinct components:\")) {\n    console.error(\"Rename transforms to unique labels across merged pipelines:\", e.message);\n  } else throw e;\n}","preventionTips":["Give transforms explicit unique names in each pipeline","Check for duplicate auto-generated component ids before merging","Avoid identical default transform names across merged pipelines"],"tags":["merge","components","conflict"],"backgroundTag":"internal-invariant-violation","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}