terraform icon indicating copy to clipboard operation
terraform copied to clipboard

Confusing behaviour when applying a plan against a workspace that doesn't match the plan

Open SarahFrench opened this issue 4 weeks ago • 1 comments

Terraform Version

% terraform version
Terraform v1.15.0-dev
on darwin_arm64

Terraform Configuration Files

Any configuration can be used to reproduce this issue.

Debug Output

N/A

Expected Behavior

Terraform should raise an error, or at least warning, if a terraform apply command tries to apply a plan against a workspace that doesn't match the workspace that the plan was prepared against. An explicit message saying something like 'the plan expected workspace A but the working directory currently has workspace B selected' would help users figure out what's gone wrong in their workflow.

Actual Behavior

Assume that a plan was raised against workspace A and a user tries to apply it against workspace B.

The actual behaviour varies depending on the type of plan and what state already exists for workspace B.

  1. If the plan is create-only, starting from a position of empty state, and workspace B has no state yet then the apply will complete successfully
  2. If the plan is create-only, starting from a position of empty state, and workspace B has a prior state then the apply will fail with error Saved plan is stale.
  3. If the plan is changing existing resources, i.e. planned using prior state in workspace A, and workspace B has no prior state then the apply will fail with a different state lineage error.

In case 2 & 3 the apply stops before making any changes to state, however the error messages don't clearly point out that mismatched workspaces are the underlying cause.

In case 1 users will find that the new state file is persisted in a different location than they expected. The output from the apply command doesn't say which workspace the apply affected, so the user might not notice the issue immediately.

Steps to Reproduce

See https://github.com/hashicorp/terraform/pull/37919 for E2E tests reproducing the 3 cases above.

To reproduce the 3 cases I described above:

Case 1

  • terraform init
  • While default workspace selected, run terraform plan -out=tfplan to create a plan file
  • Run terraform workspace create foobar to create and select a custom workspace
  • Run terraform apply tfplan to apply the plan file
  • See that new state is created for the custom workspace

Case 2

  • terraform init
  • Run terraform workspace create foobar to create and select a custom workspace
  • terraform apply to create some state in the custom workspace
  • terraform workspace select default to change workspace to the empty default workspace
  • Run terraform plan -out=tfplan to create a plan file.
    • This plan describes creating resources from an empty state starting point
  • terraform workspace select foobar to change to the custom workspace again
  • Run terraform apply tfplan to apply the plan file
  • There should be an error reporting the plan is stale.

Case 3

  • terraform init
  • terraform apply to create state in the default workspace
  • Change the configuration so that there's a diff when plan is run next
  • Run terraform plan -out=tfplan to create a plan file.
    • The plan will describe changing existing resources
  • Run terraform workspace create foobar to create and select a custom workspace
  • Run terraform apply tfplan to apply the plan file
  • There should be an error reporting that there's a mismatch of state lineage.

Additional Context

The BackendForLocalPlan method has a godoc comment that suggests that it asserts the plan's workspace matches the current workspace:

https://github.com/hashicorp/terraform/blob/64015ca6d266d259823c9e172fa911650a0d15ec/internal/command/meta_backend.go#L330-L332

It doesn't look like this was implemented at the time (see commit) that method was written.

This work has also touched the BackendForLocalPlan method https://github.com/hashicorp/terraform/pull/25262 but this just validates that the current workspace name is valid. It doesn't include comparison to the value in the plan file.

I'm going to open a PR adding that assertion, but I'll also ask around to see if this is something that was purposefully dropped in the past. Currently Terraform does protect against any existing state being affected in these scenarios, and there might be considerations around HCP Terraform that make this assertion trickier to implement smoothly for all backends in use.

References

  • Reproductions in E2E tests: https://github.com/hashicorp/terraform/pull/37919

Generative AI / LLM assisted development?

No response

SarahFrench avatar Dec 01 '25 11:12 SarahFrench

FWIW at the time it was certainly our intention for the apply phase to either check that the working directory state is consistent with the information recorded in the plan, or to use the information from the plan while ignoring the working directory state, depending on the situation. I believe the first option of checking that they both agree currently applies to the dependency lock file, for example.

I share your suspicion that this either got missed in all of the churn of reworking the state/plan models for Terraform v0.12, or some later refactoring caused that extra check to be accidentally dropped.

I can't think of any good reason to apply a plan to a different workspace than it was created in, because as you noted it cannot work unless the latest state snapshot in both workspaces is completely identical.

And both workspaces having no prior state at all is the only reasonable way for that to be true, as you noted, due to the lineage/serial mechanisms intentionally making them differ for this very reason: although the details of how such a mistake can occur changed a lot in the meantime with the introduction of backends and workspaces, accidentally changing things in the wrong place was what motivated the "lineage" concept in the first place. (terraform remote config was used to similar effect as terraform workspace select before workspaces were a thing.)

apparentlymart avatar Dec 01 '25 17:12 apparentlymart