apache/beam · error

bad function

Error message

bad function

What it means

DecodeMultiEdge wraps decodeFn failures as 'bad function' (context 'decoding userfn'). When deserializing a graph, the encoded Fn reference (payload + config) must resolve back into a DoFn/CombineFn; failures include unresolvable function paths, missing types, or payload decode errors on the receiving (often remote worker) side.

Solutions

  1. Read the wrapped decodeFn error: path-resolution vs payload-decode tells you which side is stale
  2. Rebuild/redeploy the worker with the latest user code so all fn types resolve
  3. Rebuild the pipeline and resubmit after any package refactor (avoid renaming/moving DoFns between submit and run)
  4. Register all custom types used in fn payloads so payload unmarshaling succeeds

Example fix

// before
// worker binary built before adding type myFn; graph references myFn -> bad function
// after
go build -o worker ./cmd/worker // rebuild worker with current user code, then resubmit
Defensive patterns

Strategy: try-catch

Validate before calling

func canDecodeFn(ref *pipepb.FunctionRef) error {
  fnT, err := typeForPath(ref.GetFn().GetPath())
  if err != nil { return fmt.Errorf("fn path %q unresolved: %w", ref.GetFn().GetPath(), err) }
  return validatePayloadTypes(ref, fnT)
}

Type guard

func fnResolvable(path string) bool {
  _, err := typeForPath(path)
  return err == nil
}

Try / catch

u, err := decodeFn(edge.Fn)
if err != nil {
  return fmt.Errorf("decoding userfn %v: fn %q missing or payload invalid: %w", edge, edge.Fn.GetFn().GetPath(), err)
}

Prevention

When it happens

Trigger: DecodeMultiEdge called from makeLink on an edge with edge.Fn != nil where decodeFn fails: the fn's package path/type cannot be resolved (code not on the worker), the serialized payload cannot be unmarshaled, or registered types are missing in the decoding process.

Common situations: Remote execution where the worker binary is stale and lacks the pipeline's DoFn types; package refactor moving functions so recorded paths no longer resolve; version mismatch between submitting SDK and worker; unregistered custom config types.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/63e38b8f0729cc40. Report an issue: GitHub.

Appendix: source

Thrown at sdks/go/pkg/beam/core/runtime/graphx/serialize.go:105

	return ret, nil
}

// DecodeMultiEdge converts the wire representation into the preprocessed
// components representing that edge. We deserialize to components to avoid
// inserting the edge into a graph or creating a detached edge.
func DecodeMultiEdge(edge *v1pb.MultiEdge) (graph.Opcode, *graph.Fn, *window.Fn, []*graph.Inbound, []*graph.Outbound, error) {
	var u *graph.Fn
	var wfn *window.Fn
	var inbound []*graph.Inbound
	var outbound []*graph.Outbound

	opcode := graph.Opcode(edge.Opcode)

	if edge.Fn != nil {
		var err error
		u, err = decodeFn(edge.Fn)
		if err != nil {
			wrapped := errors.Wrap(err, "bad function")
			return "", nil, nil, nil, nil, errors.WithContextf(wrapped, "decoding userfn %v", edge)
		}
	}
	if edge.WindowFn != nil {
		wfn = decodeWindowFn(edge.WindowFn)
	}
	for _, in := range edge.Inbound {
		kind, err := decodeInputKind(in.Kind)
		if err != nil {
			wrapped := errors.Wrap(err, "bad input kind")
			return "", nil, nil, nil, nil, errors.WithContextf(wrapped, "decoding userfn %v", edge)
		}
		t, err := decodeFullType(in.Type)
		if err != nil {
			wrapped := errors.Wrap(err, "bad input type")
			return "", nil, nil, nil, nil, errors.WithContextf(wrapped, "decoding userfn %v", edge)
		}
		inbound = append(inbound, &graph.Inbound{Kind: kind, Type: t})

View on GitHub (pinned to 12126d8942)