hashicorp/terraform · error

error in marshalPlanVariables

Error message

error in marshalPlanVariables: %s

What it means

Top-level propagation error from Marshal: marshalPlanVariables returned an error while decoding the plan's VariableValues against the module's variable declarations. The wrapped error identifies which variable failed.

Solutions

  1. Inspect the chained %s error for the specific variable and decode failure.
  2. Regenerate the plan with the current config so variable types match.
  3. Ensure the same config revision and Terraform version are used for plan and render.
  4. Check for recently changed/removed input variables in variables.tf.
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every plan variable decodes against its declared type before marshaling.
func varsDecodeOK(vars map[string]plans.DynamicValue, decls map[string]*configs.Variable) error {
    for k, v := range vars {
        decl, ok := decls[k]
        if !ok {
            return fmt.Errorf("variable %%s in plan but not in config", k)
        }
        if _, err := v.Decode(decl.Type); err != nil {
            return fmt.Errorf("variable %%s: %%w", k, err)
        }
    }
    return nil
}

Prevention

When it happens

Trigger: A variable value stored in the plan cannot be decoded to the type declared in the module (type drift between plan and config), or a variable present in the plan is missing from the config declarations.

Common situations: Config edited (variable type changed, variable removed/renamed) after the plan was created; cross-version plan rendering; CI using a different config revision than the planner.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/ddc281b0207dd57a. Report an issue: GitHub.

Appendix: source

Thrown at internal/command/jsonplan/plan.go:266

}

// Marshal returns the json encoding of a terraform plan.
func Marshal(
	config *configs.Config,
	p *plans.Plan,
	sf *statefile.File,
	schemas *terraform.Schemas,
) ([]byte, error) {
	output := newPlan()
	output.TerraformVersion = version.String()
	output.Timestamp = p.Timestamp.Format(time.RFC3339)
	output.Applyable = p.Applyable
	output.Complete = p.Complete
	output.Errored = p.Errored

	err := output.marshalPlanVariables(p.VariableValues, config.Module.Variables)
	if err != nil {
		return nil, fmt.Errorf("error in marshalPlanVariables: %s", err)
	}

	// output.PlannedValues
	err = output.marshalPlannedValues(p.Changes, schemas)
	if err != nil {
		return nil, fmt.Errorf("error in marshalPlannedValues: %s", err)
	}

	// output.ResourceDrift
	if len(p.DriftedResources) > 0 {
		// In refresh-only mode, we render all resources marked as drifted,
		// including those which have moved without other changes. In other plan
		// modes, move-only changes will be included in the planned changes, so
		// we skip them here.
		var driftedResources []*plans.ResourceInstanceChangeSrc
		if p.UIMode == plans.RefreshOnlyMode {
			driftedResources = p.DriftedResources
		} else {

View on GitHub (pinned to d32a084675)