Skip to content

Use resource identity to detect two state entries tracking the same remote object #39332

Open

Description

@kamadakohei

TL;DR

State can silently hold two addresses bound to one remote object. When that happens, terraform plan shows a routine-looking destroy that actually targets live infrastructure, with no hint that the object is also tracked elsewhere. An earlier ID-based duplicate check was deliberately relaxed (#33390 → #33434) because raw import IDs are not a sound uniqueness key — but resource identity (v1.12+) is exactly that key, and HCP Terraform already uses it in its bulk-import workflow to detect objects managed by another workspace. I propose bringing the same detection into core, within a single state: warn at plan time when two managed instances share an identity, and at import time when the imported object is already tracked.

Use Cases

A real incident:

  1. An AWS Secrets Manager secret was bound to a root-level address, likely via an old ad-hoc terraform import or a local apply of uncommitted config.
  2. The same secret is properly managed by a module instance (module.app.aws_secretsmanager_secret.rds_password). State ended up with two entries pointing to one remote object.
  3. The root-level address had no configuration, so plan proposed a destroy. Applying it would have scheduled deletion of the live secret referenced by running ECS tasks.
  4. Nothing in the plan output indicated the duplication. A human reviewer caught it by noticing the name collision.

Duplicate tracking commonly arises from CLI import to a second address, local applies of never-merged config, or module refactors that import into the new address without removing the old entry. The resulting destroy diffs are indistinguishable from legitimate ones.

The import documentation states the assumption but nothing enforces it:

Terraform expects that each remote object it is managing will be bound to only one resource address. (...) If you import the same object multiple times, Terraform may exhibit unwanted behavior.

Current Behavior

  • plan performs no duplicate detection across state entries.
  • CLI terraform import performs no check against existing state.
  • Config-driven import validates only duplicate target addresses among import blocks: internal/configs/module.go emits "Duplicate import configuration for %q" when two blocks share a to address. An earlier ID-based duplicate check was deliberately relaxed in configs: remove import block duplicate id check #33434 (closing "import" block validation is failing on false-positive "duplicates" #33390), on the grounds that raw ID strings are not unique: abstract resources such as time_sleep legitimately return identical import responses for distinct instances, and as noted there, "Terraform cannot enforce any uniqueness in resources" from the ID alone.
  • HCP Terraform uses resource identities in its bulk-import workflow "to determine when resources are managed by another workspace" (bulk import docs). The detection capability exists; it is not applied within a single state in core.

Proposal

Resource identity (v1.12+) resolves the objection that led to relaxing the earlier check. Unlike a raw ID string, an identity is provider-defined with a documented uniqueness contract: it "must correspond to at most one remote object per provider" (identity docs). Core already persists it per instance: ResourceInstanceObject.Identity is serialized into state as IdentityJSON with IdentitySchemaVersion (internal/states/instance_object.go). So the data needed for comparison is already in state.

  1. Plan time (primary): warn when two managed instances of the same resource type in the same state share an identity, naming both addresses. This catches pre-existing duplicates — like the incident above — exactly when a reviewer is looking at the dangerous destroy.
  2. Import time: apply the same check when an import block or CLI terraform import targets an object whose identity matches an existing managed instance.

Scope the check to resource types whose provider defines an identity schema, and skip the rest. That avoids re-introducing the #33390 false positives by construction: abstract resources like time_sleep have no meaningful remote identity and would simply not participate. Defaulting to a warning rather than a hard error keeps intentional workflows (e.g. deliberate co-management patterns) working, while making the documented assumption visible when violated. A flag could opt into an error.

Attempted Solutions

  • Process discipline (only reviewed import blocks, no local applies). This does not catch duplicates already present in state.
  • External audit in CI: terraform state pull | jq, grouping instances by (type, id). This works, but every team reinvents it, and raw string IDs are exactly what configs: remove import block duplicate id check #33434 established as unsound — and what resource identity was introduced to improve on.

Terraform Version

n/a — feature request; behavior confirmed against v1.16 documentation and current main source.

References

Activity

  1. crw commented on Oct 6, 2026

    @crw
    Contributor

    Thanks for this feature request! If you are viewing this issue and would like to indicate your interest, please use the 👍 reaction on the issue description to upvote this issue. We also welcome additional use case descriptions. Thanks again!

  2. apparentlymart commented on Oct 6, 2026

    @apparentlymart
    Contributor

    Just as some historical context, I believe this issue is describing the idea I wrote under "Detecting Object Collisions" in #22094.

    In particular, I wrote:

    Terraform will not always have sufficient information to detect this at plan time (if the Global ID is derived from values that won't be known until apply time), but in that case it would degenerate to the first case above of detecting the conflict during a subsequent plan and requiring some sort of resolution. (Exactly what resolution would be possible/appropriate is an open question; perhaps Terraform would require removing all but one of the conflicting resource blocks but then skip creating Delete actions for those in the plan, assuming the user is intending the still-remaining resource block to be the "owner" of that previously-shared object.)

    This issue proposes making this situation be reported as a warning rather than an error so that it doesn't block other changes, which seems reasonable.

    I think there's still an unanswered question of what exactly the operator ought to do when they encounter this warning. If they encounter it at plan time before the duplicate bindings exist in the state then of course what they ought to do is not apply the plan. But there are at least a few situations where this would be detected only after the duplicate binding was already persisted in a state snapshot, such as:

    1. If the operator saw the warning during planning but applied the plan anyway.
    2. If the resource identity was not known during the initial creation plan and the duplication was detected only after the plan was applied.

    In these situations I think it's important not to guide the operator into the trap of just deleting the duplicate resource blocks and running terraform apply, because that would then delete the underlying object completely and invalidate the one binding the operator actually wanted to keep.

    A manual solution would be to add a removed block for each removed resource block that explicitly disables destroying:

    removed {
      from = aws_secretsmanager_secret.rds_password
      lifecycle {
        destroy = false
      }
    }

    ...but when I wrote that earlier issue I was considering that Terraform could perhaps detect this situation -- where the resource identity of an object proposed for deletion matches the resource identity of another object that's still in the desired state -- and either automatically treat it as if destroy = false, or to emit another warning/error suggesting that the operator should add a removed block like the above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions