#39139·terraform

Inverted TargetContains check in backend_apply emits false warnings and misses partial target overrides

Author: Abhirup0Created Sep 6, 2026Updated Sep 16, 2026
Labelsbug

Terraform Version

Terraform v1.17.0-dev / main (commit c571b6ac82)

Terraform Configuration Files

terraform {
  required_providers {
    null = {
      source = "hashicorp/null"
    }
  }
}

resource "null_resource" "a" {}
resource "null_resource" "b" {}

Debug Output

N/A (Behavior is observable directly in CLI standard output and reproducible via unit test)

Expected Behavior

When applying a saved plan that contains multiple targeted resources (e.g. both null_resource.a and null_resource.b), passing -target=null_resource.a to apply cannot narrow down or filter the already-computed changes in the plan file.

Terraform should warn the user that the plan file contains additional targets (null_resource.b) that will still be applied despite the -target=null_resource.a flag.

Actual Behavior

Terraform runs completely silently with zero warnings and applies both resources:

null_resource.a: Creating...
null_resource.b: Creating...
null_resource.a: Creation complete after 0s [id=6482619472648123912]
null_resource.b: Creation complete after 0s [id=3194827501928472911]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

The user is given no indication that their -target flag was disregarded and that null_resource.b was applied.

Steps to Reproduce

  1. Create a plan with two targets: terraform plan -target=null_resource.a -target=null_resource.b -out=test.tfplan

  2. Attempt to apply only one of the targets using the saved plan: terraform apply -target=null_resource.a test.tfplan

  3. Observe that both null_resource.a and null_resource.b are applied, and no warning diagnostic is emitted.

Additional Context

This relates to the recent changes added in PR #38698 (commit c571b6ac82) in internal/backend/local/backend_apply.go:

if len(op.Targets) != 0 {
    // Do target flags all match targets in the plan?
    for _, target := range op.Targets {
        found := false
        for _, planTarget := range plan.TargetAddrs {
            if target.TargetContains(planTarget) {
                found = true
                break
            }
        }
        if !found {
            // emit warning
        }
    }
}

Two separate logic problems exist in this block:

  1. Inverted containment check: target.TargetContains(planTarget) checks if the CLI target contains the plan target, rather than whether the plan target covers the CLI target.
  • If a plan targeted module.foo and the user runs apply -target=module.foo.null_resource.bar, module.foo.null_resource.bar.TargetContains(module.foo) returns false. This emits a false-positive warning saying the resource does not match a target in the plan file, even though it was included under module.foo.
  • If a plan targeted module.foo.null_resource.bar and the user runs apply -target=module.foo, it returns true and emits no warning.
  1. Subset / re-targeting blind spot: The check only verifies that each CLI target is found in plan.TargetAddrs. If plan.TargetAddrs contains [A, B] and the user passes -target=A, A matches A, so found is true and no warning is generated. However, B will still be applied without warning.

References

  • #38670 (Original issue)
  • #38698 (PR adding plan option warnings on apply)
  • Commit c571b6ac82

Generative AI / LLM assisted development?

No (Code inspection and reproduction verified locally).