opentofu/opentofu · error

invalid wire format: %w

Error message

invalid wire format: %w

What it means

UnmarshalGraph failed at the very first step: the input bytes could not be parsed as the protobuf wire format of execgraphproto.ExecutionGraph. The bytes are not a valid protobuf message of the expected schema, so no graph elements are even attempted.

Source

Thrown at internal/engine/internal/execgraph/graph_unmarshal.go:36

	"github.com/opentofu/opentofu/internal/engine/internal/execgraph/execgraphproto"
	"github.com/opentofu/opentofu/internal/lang/eval"
	"github.com/opentofu/opentofu/internal/states"
)

// UnmarshalGraph takes some bytes previously returned by [Graph.Marshal] and
// returns a graph that is functionally-equivalent to (but not necessarily
// identical to) the original graph.
//
// Because this is working with data loaded from outside OpenTofu it returns
// errors when encountering problems, but if it fails when unmarshaling an
// unmodified result from [Graph.Marshal] then that represents a bug in either
// this or that function: they should always be updated together so they are
// implementing the same file format.
func UnmarshalGraph(src []byte) (*Graph, error) {
	var root execgraphproto.ExecutionGraph
	err := proto.Unmarshal(src, &root)
	if err != nil {
		return nil, fmt.Errorf("invalid wire format: %w", err)
	}

	elems := root.GetElements()
	// During decoding we'll track the typed result ref corresponding to
	// each element from the serialized graph, which then allows us to
	// make sure that operands are actually of the types they ought to be
	// during our validation work.
	results := make([]AnyResultRef, len(elems))
	builder := NewBuilder()

	for idx, elem := range elems {
		if !elem.HasRequest() {
			// As a special case, a totally-unpopulated result is allowed to
			// coerce to any type when we're decoding operation arguments,
			// becoming the zero value of the target type.
			results[idx] = nil
			continue
		}

View on GitHub (pinned to 3561785c48)

Solutions

  1. Regenerate the serialized execution graph with a working OpenTofu run instead of repairing the bytes
  2. Verify the byte slice was produced by Graph.Marshal / the same OpenTofu engine version that is consuming it
  3. Check for truncation: compare payload length/checksum against what the producer recorded
Defensive patterns

Strategy: try-catch

Validate before calling

// Fail fast on obviously wrong payloads before graph decoding:
// reject empty/truncated buffers and non-proto artifacts.
func plausibleGraphPayload(src []byte) error {
	if len(src) == 0 {
		return fmt.Errorf("empty serialized graph payload")
	}
	var probe execgraphproto.ExecutionGraph
	if err := proto.Unmarshal(src, &probe); err != nil {
		return fmt.Errorf("not a serialized execution graph: %w", err)
	}
	return nil
}

Try / catch

g, err := execgraph.UnmarshalGraph(src)
if err != nil {
	if strings.HasPrefix(err.Error(), "invalid wire format") {
		return fmt.Errorf("corrupt or foreign payload, regenerate the graph: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Passing truncated, corrupted, or non-graph bytes to UnmarshalGraph; feeding a file that is not a serialized execution graph at all; a payload produced by a proto schema incompatible at the wire level.

Common situations: Reading a truncated or partially-written graph artifact, passing a JSON/text file where binary proto was expected, or transmission corruption between producer and consumer.

Related errors


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