Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 8 min read

Terraform Error Guide: 'Saved plan is stale' on apply

Fix Terraform's 'Saved plan is stale' error: understand why a tfplan no longer matches state after drift, out-of-band changes, or concurrent applies, and regenerate it.

  • #terraform
  • #troubleshooting
  • #errors
  • #plan

Overview

When you run terraform plan -out=tfplan and later terraform apply tfplan, Terraform records the exact state serial the plan was built against. At apply time it checks that the state has not moved since. If the state changed between plan and apply — another apply ran, someone changed infrastructure, or state was refreshed — Terraform refuses to apply the stale plan rather than acting on assumptions that no longer hold.

You will see this on terraform apply tfplan:

Error: Saved plan is stale

The given plan file can no longer be applied because the state was changed by
another operation after the plan was created.

This is a safety feature, not corruption. A saved plan is a snapshot of intended changes tied to a specific state version; once the state advances, that snapshot is no longer valid and must be regenerated.

Symptoms

  • apply tfplan fails with Saved plan is stale.
  • The plan was generated in one CI stage and applied in a later one, with a gap between.
  • Another apply (or a manual change) happened between plan and apply.
  • A terraform refresh/apply -refresh-only ran after the plan was saved.
terraform apply tfplan
Error: Saved plan is stale

The given plan file can no longer be applied because the state was changed by
another operation after the plan was created.

Common Root Causes

1. Another apply ran between plan and apply

A second pipeline or engineer applied changes to the same state after your plan was saved, advancing the state serial.

terraform state pull | grep '"serial"'
"serial": 142,

If the plan was built at serial 139, the state has moved three writes ahead — the plan is stale.

2. Out-of-band infrastructure change refreshed into state

Someone changed a resource in the console; a later refresh updated state, invalidating the saved plan’s assumptions.

terraform plan -refresh-only
Note: Objects have changed outside of Terraform
  ~ aws_instance.app
      ~ instance_type = "t3.medium" -> "t3.large"

The drift means the plan no longer reflects reality.

3. A long gap between plan and apply stages in CI

A pipeline that plans in one job and applies after a manual approval hours later can have the state move underneath it.

grep -rn 'plan -out\|apply tfplan' .github/ Makefile 2>/dev/null
terraform plan -out=tfplan        # stage: plan
terraform apply tfplan            # stage: deploy (after approval)

The longer the approval gap, the higher the chance of staleness.

4. State was refreshed or migrated between plan and apply

A backend migration, state mv/state rm, or apply -refresh-only advanced the serial after the plan was saved.

terraform state pull | grep -E '"serial"|"lineage"'
"serial": 145,
"lineage": "a1b2c3d4-..."

A changed serial (or lineage, for a re-created state) invalidates the plan.

5. The plan was built against a different workspace

Generating the plan in one workspace and applying in another points at a different state entirely.

terraform workspace show
staging

If the plan was built in default but apply runs in staging, the states differ.

6. Concurrent plan/apply on a shared module

Two runs of the same root module overlap; the first’s apply advances state before the second’s apply consumes its plan.

gh run list --workflow deploy.yml --limit 5
in_progress  deploy  ci-build-510
completed    deploy  ci-build-508

Overlapping deploy runs make stale plans likely.

Diagnostic Workflow

Step 1: Confirm the state moved

terraform state pull | grep '"serial"'

Compare the current serial to the one the plan was built against — a higher number confirms staleness.

Step 2: Check for out-of-band drift

terraform plan -refresh-only

Any reported drift means infrastructure changed outside the saved plan’s view.

Step 3: Look for a concurrent or recent apply

gh run list --workflow deploy.yml --limit 5
terraform state pull | grep '"lineage"'

A recent successful apply (or a changed lineage) explains the advance.

Step 4: Regenerate the plan against current state

terraform plan -out=tfplan

This is the fix in almost every case — a saved plan cannot be “un-staled”, only rebuilt.

Step 5: Apply the fresh plan promptly

terraform apply tfplan

Minimize the window between plan and apply so the state does not move again.

Example Root Cause Analysis

A gated CI pipeline plans, waits for approval, then applies. After a two-hour approval delay, the deploy job fails:

Error: Saved plan is stale

The given plan file can no longer be applied because the state was changed by
another operation after the plan was created.

Checking the state serial against the plan’s:

terraform state pull | grep '"serial"'
"serial": 144,

The saved plan was built at serial 141. In the approval window, a separate hotfix pipeline applied a change, advancing state to 144. The saved plan reflects the world as of serial 141 and is no longer safe to apply.

gh run list --workflow hotfix.yml --limit 3
completed  success  hotfix  ci-build-512

The hotfix run at ci-build-512 is the culprit. There is no drift to reconcile — the plan is simply outdated.

Fix: regenerate the plan against the current state and apply it immediately.

terraform plan -out=tfplan
terraform apply tfplan

The fresh plan is built at serial 144, matches current state, and applies cleanly.

Prevention Best Practices

  • Treat a saved plan as short-lived: minimize the gap between plan -out and apply tfplan, and regenerate if anything could have changed in between.
  • Serialize applies against each state with a CI concurrency group so a second run cannot advance state under a pending plan.
  • Avoid apply -refresh-only/state mv between a saved plan and its apply; do reconciliation before planning.
  • For gated pipelines with long approvals, re-plan after approval rather than applying a hours-old plan.
  • Always confirm terraform workspace show matches between plan and apply stages.
  • For triage, the free incident assistant can confirm whether a stale plan is a serial advance vs. drift and recommend the re-plan. More patterns in the Terraform guides.

Quick Command Reference

# Did the state move?
terraform state pull | grep '"serial"'

# Any out-of-band drift?
terraform plan -refresh-only

# Recent/concurrent applies + lineage check
gh run list --workflow deploy.yml --limit 5
terraform state pull | grep '"lineage"'

# Confirm the right workspace
terraform workspace show

# The fix: regenerate and apply promptly
terraform plan -out=tfplan
terraform apply tfplan

Conclusion

Saved plan is stale means the state advanced after your plan was saved, so the plan no longer matches reality and Terraform refuses to apply it. The usual root causes:

  1. Another apply ran between plan and apply.
  2. An out-of-band change was refreshed into state.
  3. A long approval gap let the state move.
  4. A refresh/migration/state mv advanced the serial.
  5. The plan was built against a different workspace.
  6. Concurrent runs on a shared module overlapped.

A saved plan cannot be salvaged — regenerate it with terraform plan -out=tfplan against the current state and apply promptly. Serializing applies and shrinking the plan-to-apply window prevents recurrence.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.