Loading...
Loading...
### Terraform Version ```shell Terraform v1.15.4 on linux_arm64 I am pretty confident the bug is applicable from Terraform >= v1.7.0 where the removed block feature was first introduced. ``` ### Terraform Configuration Files The bug is applicable to all resource types when being removed from state with `destroy=false`. I am using null_resource for the example here as suggested in the Git Issue template. Initial **main.tf**: ```terraform title="main.tf" resource "null_resource" "example" { triggers = { name = "demo" version = "v1" purpose = "show that planned_values strips this on forget" } } ``` After apply, update **main.tf** by removing the resource from state with `destroy=false`. Then generate plan json ```terraform title="main.tf" removed { from = null_resource.example lifecycle { destroy = false } } ``` ### Debug Output JSON plan from 2nd config step above: https://gist.github.com/v3n7i/70af72d99ccffe46a14296d4574dd516 _*Note how the resource, even though it is being removed from state (change action `forget`), is still getting included in the planned_values representation. The resource is also stripped of its configuration, which is misleading given the resource is not getting deleted either. Can see true config in prior state._ ### Expected Behavior The resource targeted by the removed block should have disappeared from the `planned_values` in the JSON plan output. The `planned_values` field is documented as the projected post-apply state; it represents what the state will look like once the plan is applied. After a `removed` block is applied, the resource is dropped from state by design, and should therefore not be present in `planned_values`. ### Actual Behavior The resource targeted by the `removed` block continues to appear in `planned_values` of the JSON plan output despite being scheduled for state removal. Importantly the resource representation in `planned_values` is also stripped of its prior configuration and left with null values, which is misleading as the resource is not being deleted and still holds a real configuration. This occurs because, for the resource being removed from state without deletion, the [planForget](https://github.com/hashicorp/terraform/blob/a2ad11c4f14ceea1e81c81a56c44428fc4110f20/internal/terraform/node_resource_abstract_instance.go#L511) function sets `change.After` to a null value (as intended), but since the marshaller in [internal/command/jsonplan/values.go](https://github.com/hashicorp/terraform/blob/v1.15.4/internal/command/jsonplan/values.go#L95C1-L109C83) does not filter out `Forget` actions before attempting to emit the planned attributes, the resource ends up included in `planned_values` with no actual configuration. This results in a stub `planned_values` entry that conveys neither the prior configuration of the resource nor a meaningful projected post-apply state. It also implies, by virtue of being present in `planned_values`, that the resource will exist in state after the plan is applied. It will not. `resource_changes` is unaffected by this and correctly represent the resource with `actions: ["forget"]` and fully populated `before` object, so the action itself is recorded; the inconsistency is confined to the `planned_values` representation. ### Steps to Reproduce 1. Create a **main.tf** with a resource. E.g. ```terraform title="main.tf" resource "null_resource" "example" { triggers = { name = "demo" version = "v1" purpose = "show that planned_values strips this on forget" } } ``` 2. Run `terraform init` 3. Run `terraform apply` 4. Modify **main.tf** removing the resource from state (destroy=false). E.g ```terraform title="main.tf" removed { from = null_resource.example lifecycle { destroy = false } } ``` 5. Run `terraform plan -out=plan.out` 6. Run `terraform show -json plan.out > plan.json` 7. Inspect the **plan.json** output file and observe how the resource is still present in `planned_values` with a stripped configuration. ### Additional Context IaC scanning tools like **Sentinel**, **Checkov** and **OPA** consume the JSON plan output. My team utilises Checkov to scan terraform plans and gate security vulnerabilities before they get deployed. Since `planned_values` is meant to represent the state post apply, Checkov specifically loads the resources present in this section and runs its policies against it. Given in this case the resource being removed from state remains present in `planned_values`, policies still get evaluated against it, but since its configuration is represented fully stripped, the policy will return a False Positive violation. State migrations for resources are a routine operation for uplifting modules across teams so I am sure I am not the only one who noticed this behaviour. People using IaC scanning tools are likely working around the bug, but this should really be solved at the source here. Should be a straightforward fix as well. ### References As mentioned above I am pretty sure this bug was introduced with [Terraform v1.7.0](https://github.com/hashicorp/terraform/releases/tag/v1.7.0) which saw the introduction of the removed block feature: - PR: #34251 - commit: [a718f70](https://github.com/hashicorp/terraform/commit/a718f70f85) ### Generative AI / LLM assisted development? I encountered this bug organically while investigating a false positive violation on a JSON terraform plan scan. I then utilised Claude Code with Opus 4.7 to find the lines responsible in the Terraform repo and asked to search trough the git history for the first introduction of the bug. I then verified the bug introduction on v1.7.0 using `hashicorp/terraform:1.7.0` docker image as well as latest.
Click on a version to see all relevant bugs
Terraform Integration
Learn more about where this data comes from
Bug Scrub Advisor
Streamline upgrades with automated vendor bug scrubs
BugZero Enterprise
Wish you caught this bug sooner? Get proactive today.