prevent_destroy from a variable makes destroy unplannable when a count/for_each module instance is removed
Community note
Filing this as a distinct case from #3504, which was fixed by #3507 and covered tofu destroy with the configuration intact. That path works correctly today. The case below is not covered by that fix.
OpenTofu Version
OpenTofu v1.12.3
on darwin_arm64
# also reproduced on:
OpenTofu v1.13.0-beta1
on darwin_arm64No providers are required — the repro uses the built-in terraform_data.
OpenTofu Configuration Files
mod/main.tf:
variable "name" { type = string }
variable "prevent_destroy" {
type = bool
default = true
}
resource "terraform_data" "z" {
input = var.name
lifecycle {
prevent_destroy = var.prevent_destroy
}
}main.tf:
variable "enabled" {
type = bool
default = true
}
module "single" {
count = var.enabled ? 1 : 0
source = "./mod"
name = "x"
prevent_destroy = false # literal, supplied by the caller
}Debug Output
Error: Invalid value for prevent_destroy
on mod/main.tf line 11, in resource "terraform_data" "z":
11: prevent_destroy = var.prevent_destroy
├────────────────
│ var.prevent_destroy is a bool, known only after apply
Resource instance module.single[0].terraform_data.z has a prevent_destroy
argument but its value will not be known until the apply step, so OpenTofu
can't predict whether destroying this is acceptable.
To proceed, exclude instances of this resource from this round using:
-exclude="module.single[0].terraform_data.z"Expected Behavior
The plan should succeed and show the resource being destroyed.
The documentation for prevent_destroy states:
It does not prevent destruction if the resource block is removed from the configuration.
Dropping a count to 0 (or removing a for_each key) removes that instance's configuration, so this should fall under the documented "removed from the configuration" case. It also already behaves that way when the entire module block is deleted — see the asymmetry below.
Note also that the caller supplies a literal false. Even if the value were evaluated, it is statically known and would permit the destroy.
Actual Behavior
The plan fails with Invalid value for prevent_destroy. Because this is a plan-time error, the resource cannot be destroyed through a configuration change at all — the whole plan is rejected, not just that resource.
Steps to Reproduce
tofu inittofu apply -auto-approve(createsmodule.single[0].terraform_data.z)tofu plan -var 'enabled=false'- Observe the error above.
The same happens with for_each when a key is removed from the map.
Additional Context
1. Asymmetry with deleting the module block. With byte-identical state, the outcome depends on whether the module block still exists:
| Configuration change | Result |
|---|---|
Module block kept, count 1 → 0 |
❌ Invalid value for prevent_destroy |
Module block kept, for_each key removed |
❌ Invalid value for prevent_destroy |
| Module block deleted entirely | ✅ plans clean, 1 to destroy |
tofu plan -destroy, config intact, variable true |
✅ correct Resource instance cannot be destroyed error |
tofu plan -destroy, config intact, variable false |
✅ plans clean, 1 to destroy |
Deleting the whole block takes a path that does not attempt evaluation, so the destroy proceeds. Keeping the block but removing the instance attempts to evaluate prevent_destroy in a module-instance scope that no longer exists, and the absent value surfaces as unknown.
2. A caller-supplied literal does not help. In the repro above the caller passes prevent_destroy = false as a constant. It makes no difference, because the argument belongs to a module instance that no longer exists in configuration.
3. The suggested -exclude address does not work. Passing exactly the address the error prints reproduces the same error and the same suggestion:
tofu plan -var 'enabled=false' -exclude='module.single[0].terraform_data.z'
# -> same "Invalid value for prevent_destroy" errorExcluding at the module address does work:
tofu plan -var 'enabled=false' -exclude='module.single'
# -> plans successfullyThis may warrant a separate issue; flagging it here since the error message actively directs users to a form that fails.
4. Practical impact. Any module invoked with count or for_each that sets lifecycle.prevent_destroy from a variable cannot be torn down through an ordinary configuration change. The failure only appears at teardown time, long after the pattern is adopted, and the message ("known only after apply") points toward a timing problem rather than the actual cause.
References
- #1329 — parent feature request for variables in lifecycle attributes (still open,
pending-decision) - #3504 — closely related, fixed by #3507; covers
tofu destroywith configuration intact, not this case - #3507 —
tofu: GraphNodeDestroyer can now have references - Docs: https://opentofu.org/docs/language/resources/behavior — "It does not prevent destruction if the resource block is removed from the configuration."
Source: opentofu/opentofu