apache/beam · error

buildDescriptor: invalid stage - no transforms at all

Error message

buildDescriptor: invalid stage - no transforms at all %v

What it means

buildDescriptor refuses to construct a bundle descriptor for a stage whose transform list is empty. A prism stage must contain at least one pipeline transform to execute; an empty stage means the stage-building logic received no transforms, which is an invalid pipeline partition.

Solutions

  1. Log/inspect the stage ID and pipeline graph to see why the stage got no transforms
  2. Check whether a custom optimization or fusion override dropped all transforms
  3. Simplify the pipeline graph to isolate the offending topology
  4. Report to Beam if triggered by a standard pipeline
Defensive patterns

Strategy: validation

Validate before calling

// Guard: ensure stage has transforms before descriptor build
if len(stageTransforms) == 0 {
    return fmt.Errorf("stage %s has no transforms; check pipeline plan", stageID)
}

Prevention

When it happens

Trigger: executePipeline produced a stage with zero transforms assigned, then called buildDescriptor on it.

Common situations: Runner-side stage-assignment bugs; pipelines with degenerate graph shapes (e.g. transforms all optimized away); custom runner hooks altering stage topology.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/3af105febfef1e76. Report an issue: GitHub.

Appendix: source

Thrown at sdks/go/pkg/beam/runners/prism/internal/stage.go:542

				stg.processingTimeTimers[timerID] = true
			}
			rewrite = true
			newCid, err := lpUnknownCoders(v.GetTimerFamilyCoderId(), coders, comps.GetCoders())
			if err != nil {
				return fmt.Errorf("unable to rewrite coder %v for timer %v for transform %v in stage %v: %w", v.GetTimerFamilyCoderId(), timerID, tid, stg.ID, err)
			}
			v.TimerFamilyCoderId = newCid
		}
		if rewrite {
			pyld, err := proto.MarshalOptions{}.Marshal(pardo)
			if err != nil {
				return fmt.Errorf("unable to encode ParDoPayload for %v in stage %v after rewrite", tid, stg.ID)
			}
			t.Spec.Payload = pyld
		}
	}
	if len(transforms) == 0 {
		return fmt.Errorf("buildDescriptor: invalid stage - no transforms at all %v", stg.ID)
	}

	// Start with outputs, since they're simple and uniform.
	sink2Col := map[string]string{}
	col2Coders := map[string]engine.PColInfo{}
	for _, o := range stg.outputs {
		col := clonePColToBundle(o.Global)
		wOutCid, err := makeWindowedValueCoder(o.Global, comps, coders)
		if err != nil {
			return fmt.Errorf("buildDescriptor: failed to handle coder on stage %v for output %+v, pcol %q %v:\n%w %v", stg.ID, o, o.Global, prototext.Format(col), err, stg.transforms)
		}
		sinkID := o.Transform + "_" + o.Local
		ed := collectionPullDecoder(col.GetCoderId(), coders, comps)

		var kd func(io.Reader) []byte
		if kcid, ok := extractKVCoderID(col.GetCoderId(), coders); ok {
			kd = collectionPullDecoder(kcid, coders, comps)
		}

View on GitHub (pinned to 12126d8942)