hashicorp/terraform · error

failed to marshal outputs to json: %w

Error message

failed to marshal outputs to json: %w

What it means

Thrown during PersistState after Terraform marshals root output values: json.Marshal(ov) fails on the map returned by jsonstate.MarshalOutputs. It means an internal serialization of the state's root outputs could not be JSON-encoded for the json-state-outputs field of the state-version upload. This is an internal/serialization failure rather than a normal configuration problem.

Source

Thrown at internal/cloud/state.go:228

	if schemas != nil {
		jsonState, err = jsonstate.Marshal(f, schemas)
		if err != nil {
			return err
		}
	}

	stateFile, err := statefile.Read(bytes.NewReader(buf.Bytes()))
	if err != nil {
		return fmt.Errorf("failed to read state: %w", err)
	}

	ov, err := jsonstate.MarshalOutputs(stateFile.State.RootOutputValues)
	if err != nil {
		return fmt.Errorf("failed to translate outputs: %w", err)
	}
	jsonStateOutputs, err := json.Marshal(ov)
	if err != nil {
		return fmt.Errorf("failed to marshal outputs to json: %w", err)
	}

	err = s.uploadState(s.lineage, s.serial, s.forcePush, buf.Bytes(), jsonState, jsonStateOutputs)
	if err != nil {
		s.stateUploadErr = true
		return fmt.Errorf("error uploading state: %w", err)
	}
	// After we've successfully persisted, what we just wrote is our new
	// reference state until someone calls RefreshState again.
	// We've potentially overwritten (via force) the state, lineage
	// and / or serial (and serial was incremented) so we copy over all
	// three fields so everything matches the new state and a subsequent
	// operation would correctly detect no changes to the lineage, serial or state.
	s.readState = s.state.DeepCopy()
	s.readLineage = s.lineage
	s.readSerial = s.serial

	return nil

View on GitHub (pinned to c9def3e214)

Solutions

  1. Re-run terraform refresh / terraform apply; if the state is internally valid the re-marshal usually succeeds.
  2. Inspect root output declarations for exotic or unsupported value shapes and simplify them.
  3. If reproducible, dump the offending output values and file a Terraform issue with the state version.
  4. As a workaround, remove or simplify the suspect output block and re-apply to rewrite state.

Example fix

// before: an output holding a non-encodable/internal value
output "x" { value = local.something_odd }

// after: keep outputs to plain, encodable types
output "x" { value = var.simple_string }
Defensive patterns

Strategy: try-catch

Try / catch

if err := state.PersistState(schemas); err != nil {
    // PersistState already wraps the cause with %w
    var jsonErr *json.UnsupportedTypeError
    if errors.As(err, &jsonErr) {
        // root output contains a non-encodable value type; simplify outputs and retry
        return fmt.Errorf("unencodable output value, simplify outputs: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: PersistState(schemas) is called and one of the root output values produced by jsonstate.MarshalOutputs is not JSON-encodable. In practice this requires a malformed or internally-inconsistent output value object, since well-formed outputs always serialize.

Common situations: Almost always an internal Terraform bug or a corrupted state object, e.g. after a bad state upgrade or a go-cty value that does not round-trip. Normal configs do not trigger it.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/10d443efce81eec0. Report an issue: GitHub.