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
- Re-run `pulumi preview --save-plan plan.json` to regenerate the plan against current code/config, then apply that fresh plan.
- Ensure nothing (program code, config, upstream resource outputs) changed between preview and apply; apply the plan with the exact same stack, config, and state.
- 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.
- 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
- Regenerate the plan immediately before every plan-based apply; never reuse stale plans.
- Freeze code and config between preview and apply (CI pipelines: save-plan and up in the same job).
- Avoid runtime-nondeterministic inputs (timestamps, random values) in resources covered by plans.
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
- %v is not allowed by the plan: no steps were expected for th
- Expected a new resource plan for %v
- could not parse provider reference %s for %s: %w
- normalizing resource goal for %s after state migration: %w
- no plan for resource %v
AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31).
Data as JSON: /api/errors/ffba1afac5a36d6c.
Report an issue: GitHub.