hashicorp/terraform · error

failed to marshal outputs to json

Error message

failed to marshal outputs to json: %w

What it means

Thrown during PersistState when Go's standard encoding/json.Marshal fails to serialize the output value structure produced by jsonstate.MarshalOutputs into the final JSONStateOutputs byte slice. Unlike the preceding translate step (which handles cty-to-JSON type mapping), this is pure JSON encoding of an already-translated Go structure. Failure indicates the translated structure contains a non-serializable Go value.

Solutions

  1. Upgrade terraform to the latest stable release — json marshaling bugs in outputs are fixed promptly
  2. Simplify the root outputs to isolate which value produces the unserializable structure
  3. Report the issue with the json.Marshal error text (it names the Go type that failed) and the terraform/provider versions
Defensive patterns

Strategy: validation

Validate before calling

// Validate that translated outputs are JSON-serializable before persist:
ov, err := jsonstate.MarshalOutputs(state.RootOutputValues)
if err != nil {
    return err
}
if _, err := json.Marshal(ov); err != nil {
    return fmt.Errorf("outputs are not JSON-serializable, cannot persist: %w", err)
}

Try / catch

err := stateMgr.PersistState(schemas)
if err != nil && strings.Contains(err.Error(), "failed to marshal outputs to json") {
    log.Print("output JSON marshaling failed; this is likely a terraform bug")
}
return err

Prevention

When it happens

Trigger: jsonstate.MarshalOutputs returns a structure containing a Go type json.Marshal cannot handle (func, chan, complex128, or a type with a broken MarshalJSON); the structure contains an unserializable cty.Value marker that leaked through translation; a bug in MarshalOutputs producing map keys that are not strings.

Common situations: Rare in stable releases; most commonly a terraform bug discovered in a new output type. Can appear when a provider's custom output serialization interacts badly with a terraform version change.

Related errors


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

Appendix: 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 d32a084675)