pulumi/pulumi · error

%v is not allowed by the plan: this resource is constrained

Error message

%v is not allowed by the plan: this resource is constrained to %v

What it means

During a plan-constrained deployment (pulumi preview with a plan file applied via --plan), the step generator checks each incoming resource operation against the operations the plan expects for that resource. When the runtime attempt (e.g. create, update, delete, replace) does not match the single operation the plan allows, the deployment is aborted with this error. It exists to guarantee that a `pulumi up --plan` executes exactly the steps recorded in the preview, preventing drift between preview and apply.

Source

Thrown at pkg/resource/deploy/step_generator.go:452

		steps = append(prepend, steps...)
	}

	// Check each proposed step against the relevant resource plan, if any
	for _, s := range steps {
		logging.V(5).Infof("Checking step %s for %s", s.Op(), s.URN())

		if sg.deployment.plan != nil {
			if resourcePlan, ok := sg.deployment.plan.ResourcePlans[s.URN()]; ok {
				if len(resourcePlan.Ops) == 0 {
					return nil, fmt.Errorf("%v is not allowed by the plan: no more steps were expected for this resource", s.Op())
				}
				constraint := resourcePlan.Ops[0]
				// We remove the Op from the list before doing the constraint check.
				// This is because we look at Ops at the end to see if any expected operations didn't attempt to happen.
				// This op has been attempted, it just might fail its constraint.
				resourcePlan.Ops = resourcePlan.Ops[1:]
				if !ConstrainedTo(s.Op(), constraint) {
					return nil, fmt.Errorf("%v is not allowed by the plan: this resource is constrained to %v", s.Op(), constraint)
				}
			} else {
				if !ConstrainedTo(s.Op(), OpSame) {
					return nil, fmt.Errorf("%v is not allowed by the plan: no steps were expected for this resource", s.Op())
				}
			}
		}

		// If we're generating plans add the operation to the plan being generated
		if sg.deployment.opts.GeneratePlan {
			// Resource plan might be aliased
			urn, isAliased := sg.aliased[s.URN()]
			if !isAliased {
				urn = s.URN()
			}
			if resourcePlan, ok := sg.deployment.newPlans.get(urn); ok {
				// If the resource is in the plan, add the operation to the plan.
				resourcePlan.Ops = append(resourcePlan.Ops, s.Op())

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Re-run `pulumi preview --save-plan plan.json` to regenerate the plan against current code/config, then apply that fresh plan.
  2. Ensure nothing (program code, config, upstream resource outputs) changed between preview and apply; apply the plan with the exact same stack, config, and state.
  3. If the resource's operation legitimately depends on runtime data, do not use plan-based apply (drop --plan) so the engine can decide ops freely.
  4. Check that the plan file was generated for the same project/stack and that the resource's URN is unchanged (no rename, parent, or type changes).

Example fix

// before: preview then changing config
pulumi preview --save-plan plan.json
pulumi config set featureFlag true
pulumi up --plan plan.json   # op no longer matches plan -> error

// after: regenerate the plan after any change
pulumi config set featureFlag true
pulumi preview --save-plan plan.json
pulumi up --plan plan.json
Defensive patterns

Strategy: validation

Validate before calling

// Before applying a plan, re-preview with the plan to verify ops still match:
pulumi preview --plan plan.json   # fails fast if the program diverges from the plan

Try / catch

// In automation against the automation API:
try {
    await stack.up({ plan: "plan.json" });
} catch (err) {
    if (String(err).includes("is not allowed by the plan")) {
        // regenerate the plan and retry
        await stack.preview({ savePlan: "plan.json" });
        await stack.up({ plan: "plan.json" });
    } else { throw err; }
}

Prevention

When it happens

Trigger: Running `pulumi up --plan plan.json` when the program's behavior at apply time differs from what the recorded preview planned for a resource — e.g. the plan expects OpSame but the engine attempts an update/delete, or the plan expects a specific op but the resource produces a different one.

Common situations: Editing Pulumi program code or configuration between preview and apply while reusing the plan file; resource inputs depending on runtime values (computed outputs, dynamic providers, external side effects) that resolve differently at apply time; using a plan generated against a different stack/state.

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/ffba1afac5a36d6c. Report an issue: GitHub.