apache/beam · error

invalid status for plan %v: %v

Error message

invalid status for plan %v: %v

What it means

After performing Up and initializing splittable sources, Plan.Execute requires plan status Up before processing a bundle. This error means Execute was called on a plan that is not in the Up state - typically a plan that already ran (Active/Broken) or was never reset. The library enforces one bundle lifecycle per status transition; after execution the plan is Active and later Broken, never re-usable.

Source

Thrown at sdks/go/pkg/beam/core/runtime/exec/plan.go:137

// Execute executes the plan with the given data context and bundle id. Units
// are brought up on the first execution. If a bundle fails, the plan cannot
// be reused for further bundles. Does not panic. Blocking.
func (p *Plan) Execute(ctx context.Context, id string, manager DataContext) error {
	if p.getStatus() == Initializing {
		for _, u := range p.units {
			if err := callNoPanic(ctx, u.Up); err != nil {
				p.setStatus(Broken)
				return errors.Wrapf(err, "while executing Up for %v", p)
			}
		}
		p.setStatus(Up)
	}
	if p.source != nil {
		p.source.InitSplittable()
	}

	if s := p.getStatus(); s != Up {
		return errors.Errorf("invalid status for plan %v: %v", p.id, s)
	}

	// Process bundle. If there are any kinds of failures, we bail and mark the plan broken.

	p.setStatus(Active)
	for _, root := range p.roots {
		if err := callNoPanic(ctx, func(ctx context.Context) error { return root.StartBundle(ctx, id, manager) }); err != nil {
			p.setStatus(Broken)
			return errors.Wrapf(err, "while executing StartBundle for %v", p)
		}
	}
	for _, root := range p.roots {
		if err := callNoPanic(ctx, func(ctx context.Context) error {
			cps, err := root.Process(ctx)
			p.checkpoints = cps
			return err
		}); err != nil {
			p.setStatus(Broken)

View on GitHub (pinned to 12126d8942)

Solutions

  1. Construct a new Plan (via NewPlan/constructAndExecutePlan) for every bundle instead of reusing a previously executed plan.
  2. Do not call Execute on a plan that previously returned an error - it is Broken and terminal.
  3. If resuming SDF bundles, the split plan creation path (createSdfPlan) already builds a fresh plan; use that flow.

Example fix

// before
plan.Execute(ctx, bundle1, mgr)
plan.Execute(ctx, bundle2, mgr) // invalid status: not Up
// after
for _, bundle := range bundles {
    plan, err := constructAndExecutePlanWithContext(ctx, planID, units, bundle, mgr)
    if err != nil { return err }
}
Defensive patterns

Strategy: validation

Validate before calling

// Plans are single-use per bundle lifecycle: always build a fresh plan.
plan, err := constructAndExecutePlanWithContext(ctx, planID, units, bundleID, mgr)
if err != nil {
    return err
}

Try / catch

if err := plan.Execute(ctx, id, mgr); err != nil {
    if strings.Contains(err.Error(), "invalid status for plan") {
        plan, err = constructAndExecutePlanWithContext(ctx, id, units, id, mgr)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Plan.Execute twice on the same plan for consecutive bundles; calling Execute on a Broken plan after a previous bundle failure; caching and reusing plans in a runner loop.

Common situations: Runner worker loops that keep a plan object around between bundles; test code executing the same plan for multiple test cases; resuming from a checkpoint without constructing a new plan.

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


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