opentofu/opentofu · error

failed to decode backend config: %w

Error message

failed to decode backend config: %w

What it means

Raised by BackendState.ForPlan (internal/command/clistate/cli_state.go:95) when the backend configuration cached in the CLI state file (.terraform/terraform.tfstate) cannot be decoded with ctyjson.Unmarshal against the backend schema's implied type. The cached raw config JSON no longer matches what the current backend schema accepts, so building the plans.Backend value fails.

Source

Thrown at internal/command/clistate/cli_state.go:95

func (b *BackendState) SetConfig(val cty.Value, schema *configschema.Block) error {
	ty := schema.ImpliedType()
	buf, err := ctyjson.Marshal(val, ty)
	if err != nil {
		return err
	}
	b.ConfigRaw = buf
	return nil
}

func (b *BackendState) ForPlan(schema *configschema.Block, workspaceName string) (*plans.Backend, error) {
	if b == nil {
		return nil, nil
	}

	configVal, err := b.Config(schema)
	if err != nil {
		return nil, fmt.Errorf("failed to decode backend config: %w", err)
	}
	return plans.NewBackend(b.Type, configVal, schema, workspaceName)
}

var ErrNoState = errors.New("no state")

type jsonVersionOnly struct {
	Version int `json:"version"`
}

// ReadState reads the CLI state file format written by WriteState.
// dataDirOverridden should be true when the data directory was explicitly
// overridden (e.g. via TF_DATA_DIR).
func ReadState(src io.Reader, dataDirOverridden bool) (*CLIState, error) {
	if f, ok := src.(*os.File); ok && f == nil {
		return nil, ErrNoState
	}

View on GitHub (pinned to 3561785c48)

Solutions

  1. Run 'tofu init -reconfigure' to rewrite the backend cache from the current configuration, then retry the plan
  2. If reconfigure is not wanted, remove the stale cache file (.terraform/terraform.tfstate) and run 'tofu init'
  3. Verify the backend block in the configuration has attribute names/types valid for the backend (typos produce unexpected-attribute values)
  4. Align all team/CI environments on one OpenTofu version so the cached schema matches

Example fix

# before
tofu plan
# Error: failed to decode backend config: ...

# after
tofu init -reconfigure
tofu plan
Defensive patterns

Strategy: fallback

Validate before calling

// decode the cached config against the schema BEFORE planning:
if bs != nil && !bs.Empty() {
    if _, err := bs.Config(backendSchema); err != nil {
        // stale/incompatible cache: refresh instead of failing later
        _ = runInitReconfigure()
    }
}

Try / catch

backend, err := b.ForPlan(schema, ws)
if err != nil {
    if strings.Contains(err.Error(), "failed to decode backend config") {
        // cache/schema drift: regenerate with 'tofu init -reconfigure', then retry once
        backend, err = afterReinitForPlan(b, schema, ws)
    }
    if err != nil {
        return err
    }
}

Prevention

When it happens

Trigger: ForPlan called with a schema whose ImpliedType differs from the type the cached ConfigRaw was marshalled with: running plan after OpenTofu changed the backend's schema between versions, after editing the backend block to a type with different attributes, or against a .terraform directory written by a different tool version.

Common situations: Upgrading or downgrading OpenTofu without re-running init; a teammate's .terraform directory committed or copied between checkouts with different backend configs; hand-edited cache files; switching between local and remote backend configs without -reconfigure.

Understand the failure class

Related errors


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