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
- Log/inspect the stage ID and pipeline graph to see why the stage got no transforms
- Check whether a custom optimization or fusion override dropped all transforms
- Simplify the pipeline graph to isolate the offending topology
- 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
- Avoid custom graph optimizations that can drop all transforms
- Keep runner internals up to date
- Validate pipeline graphs with standard runners first
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
- buildDescriptor: couldn't retrieve coder
- buildDescriptor: couldn't rewrite coder
- buildDescriptor: failed to handle coder on stage
- buildDescriptor: failed to handle coder on stage
- buildDescriptor: failed to handle coder on stage
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)