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

  1. Regenerate the plan with the same binary that will apply it and re-apply
  2. Pin the OpenTofu version that created the plan (check the version_stamp in `tofu show -json plan.tfplan`) and retry with it
  3. 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

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


AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15). Data as JSON: /api/errors/db8d76b650a36712. Report an issue: GitHub.