apache/beam · error · ValueError
Stage has no main inputs. At least one main input is…
Error message
Stage %s has no main inputs. At least one main input is necessary.
What it means
The Fn API runner's watermark manager requires every execution stage to have at least one main input so watermarks can propagate; `_verify` (called from __init__) raises ValueError when a stage has zero main inputs. It dumps the offending stage via visualization_tools before raising to aid debugging.
Solutions
- Inspect the printed stage (visualization_tools.show_stage) to see why its inputs were dropped
- Check recent custom transforms/translations that may create stages without main inputs
- File/check a Beam issue: this usually indicates a runner-side graph-construction bug, not user error
- Work around by restructuring the pipeline so the stage has a real main input PCollection
Defensive patterns
Strategy: validation
Validate before calling
for stage in stages:
if not stage.inputs:
raise ValueError(f'Stage {stage.name} has no main inputs; fix graph construction before WatermarkManager init') Type guard
def has_main_inputs(stage): return bool(stage.inputs)
Prevention
- Never create translations.Stage objects without at least one main input PCollection
- Run pipeline-graph validation/visualization before runner execution
- Keep custom translations in sync with upstream Beam graph rewrites
When it happens
Trigger: Building a WatermarkManager over a set of translations.Stage objects where some stage's `inputs` dict is empty — e.g. a graph-transformation step produced an isolated stage or incorrectly dropped its input PCollections.
Common situations: Graph rewriting bugs in fn_api_runner translations; a stage whose only inputs were consumed as side inputs or removed by an optimization; misconfigured pipeline during portability work.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Could not find subtransform to copy:
- Input to Impulse transform must be a PBegin but found
- set_watermark expects a Timestamp as input
- Watermark must be monotonically increasing.Provided…
- Watermark must be monotonically increasing. Provided
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/7be28fbe04e84cd2.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/python/apache_beam/runners/portability/fn_api_runner/watermark_manager.py:184
# 4. Get stage side inputs, create nodes for them, add to
# _pcollections_by_name, and add them as side inputs of the stage.
for pcoll_name in s.side_inputs():
if pcoll_name not in self._pcollections_by_name:
self._pcollections_by_name[
pcoll_name] = WatermarkManager.PCollectionNode(pcoll_name)
pcoll_node = self._pcollections_by_name[pcoll_name]
assert isinstance(pcoll_node, WatermarkManager.PCollectionNode)
stage_node.side_inputs.add(pcoll_node)
self._verify(stages)
def _verify(self, stages: list[translations.Stage]):
for s in stages:
if len(self._stages_by_name[s.name].inputs) == 0:
from apache_beam.runners.portability.fn_api_runner import visualization_tools
visualization_tools.show_stage(s)
raise ValueError(
'Stage %s has no main inputs. '
'At least one main input is necessary.' % s.name)
def get_stage_node(self, name: str) -> StageNode:
# noqa: F821
return self._stages_by_name[name]
def get_pcoll_node(self, name: str) -> PCollectionNode:
# noqa: F821
return self._pcollections_by_name[name]
def set_pcoll_watermark(self, name, watermark):
element = self._pcollections_by_name[name]
element.set_watermark(watermark)
View on GitHub (pinned to 12126d8942)