opentofu/opentofu · error
saved backend configuration is invalid: %w
Error message
saved backend configuration is invalid: %w
What it means
Meta.BackendForLocalPlan decodes the plan's saved backend configuration against the backend's current schema (settings.Config.Decode(schema.ImpliedType())) and the decode failed. The stored configuration no longer fits the shape this backend expects, usually because the backend's schema changed between when the plan was written and when it is applied. The underlying cty decode error is wrapped with %w for detail.
Source
Thrown at internal/command/meta_backend.go:362
f, canonType := backendInit.Backend(settings.Type)
if f == nil {
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), settings.Type))
return nil, diags
}
if canonType != settings.Type {
// We should always save the canonical name in a plan -- never an alias
// name -- so getting here suggests a bug in the code that generated
// this plan.
diags = diags.Append(fmt.Errorf("saved plan should use canonical backend type %q, not alias %q; this is a bug in OpenTofu", canonType, settings.Type))
return nil, diags
}
b := f(enc)
log.Printf("[TRACE] Meta.BackendForLocalPlan: instantiated backend of type %T", b)
schema := b.ConfigSchema()
configVal, err := settings.Config.Decode(schema.ImpliedType())
if err != nil {
diags = diags.Append(fmt.Errorf("saved backend configuration is invalid: %w", err))
return nil, diags
}
newVal, validateDiags := b.PrepareConfig(configVal)
diags = diags.Append(validateDiags)
if validateDiags.HasErrors() {
return nil, diags
}
configureDiags := b.Configure(ctx, newVal)
diags = diags.Append(configureDiags)
if configureDiags.HasErrors() {
return nil, diags
}
// If the backend supports CLI initialization, do it.
if cli, ok := b.(backend.CLI); ok {
cliOpts, err := m.backendCLIOpts(ctx)View on GitHub (pinned to 3561785c48)
Solutions
- Regenerate the plan with the same binary that will apply it and re-apply
- Pin the OpenTofu version that created the plan (check the version_stamp in `tofu show -json plan.tfplan`) and retry with it
- If the plan file is suspect, verify integrity (size, checksum, re-download) before regenerating
Example fix
# before # plan made with 1.6, applying with 1.8 whose s3 backend schema changed tofu apply cached.tfplan # after tofu init && tofu plan -out=fresh.tfplan && tofu apply fresh.tfplan
Defensive patterns
Strategy: validation
Validate before calling
#!/usr/bin/env bash
# Refuse to apply a plan made by a different version
PLAN_V=$(tofu show -json plan.tfplan | jq -r '.prior_state?.terraform_version, .planned_values? // empty | select(type=="string")' 2>/dev/null | head -1)
MY_V=$(tofu version -json | jq -r .terraform_version)
[ "$PLAN_V" = "$MY_V" ] || { echo "plan built by $PLAN_V, applying with $MY_V; regenerate" >&2; exit 1; }
tofu apply plan.tfplan Prevention
- Pin tofu version in CI and locally (mise/asdf/opentofu-version file)
- Regenerate plans after any backend schema-affecting upgrade; never apply stale plans
- Checksum plan artifacts after generation and reject mismatches before apply
- Keep plan creation and apply in the same job or same-version jobs
When it happens
Trigger: `tofu apply plan.tfplan` where the plan was generated by a different OpenTofu version whose backend schema (attribute names/types) differs; corrupt or truncated plan files; plans produced by forks with extended backend config.
Common situations: Applying yesterday's plan after a version bump that changed a backend's attributes; sharing plan artifacts across a team with mixed versions; partial downloads / truncated artifacts; CI caching plans across upgrades.
Related errors
- The backend %q could not be found. This is the backend that
- ephemeral resources are not meant to be stored in the plan f
- saved plan should use canonical backend type %q, not alias %
- default workspace not supported You can create a new workspa
- empty state name
AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15).
Data as JSON: /api/errors/db8d76b650a36712.
Report an issue: GitHub.