{"record":{"id":"5569d52dcc0c5dc8","repo":"apache/beam","slug":"bad-userfn","errorCode":null,"errorMessage":"bad userfn","messagePattern":"bad userfn","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sdks/go/pkg/beam/core/runtime/graphx/serialize.go","lineNumber":49,"sourceCode":"\t\"github.com/apache/beam/sdks/v2/go/pkg/beam/core/timers\"\n\t\"github.com/apache/beam/sdks/v2/go/pkg/beam/core/typex\"\n\t\"github.com/apache/beam/sdks/v2/go/pkg/beam/core/util/jsonx\"\n\t\"github.com/apache/beam/sdks/v2/go/pkg/beam/core/util/reflectx\"\n\t\"github.com/apache/beam/sdks/v2/go/pkg/beam/internal/errors\"\n)\n\nvar genFnType = reflect.TypeOf((*func(string, reflect.Type, []byte) reflectx.Func)(nil)).Elem()\n\n// EncodeMultiEdge converts the preprocessed representation into the wire\n// representation of the multiedge, capturing input and output type information.\nfunc EncodeMultiEdge(edge *graph.MultiEdge) (*v1pb.MultiEdge, error) {\n\tret := &v1pb.MultiEdge{}\n\tret.Opcode = string(edge.Op)\n\n\tif edge.DoFn != nil {\n\t\tref, err := encodeFn((*graph.Fn)(edge.DoFn))\n\t\tif err != nil {\n\t\t\twrapped := errors.Wrap(err, \"bad userfn\")\n\t\t\treturn nil, errors.WithContextf(wrapped, \"encoding userfn %v\", edge)\n\t\t}\n\t\tret.Fn = ref\n\t}\n\tif edge.CombineFn != nil {\n\t\tref, err := encodeFn((*graph.Fn)(edge.CombineFn))\n\t\tif err != nil {\n\t\t\twrapped := errors.Wrap(err, \"bad combinefn\")\n\t\t\treturn nil, errors.WithContextf(wrapped, \"encoding userfn %v\", edge)\n\t\t}\n\t\tret.Fn = ref\n\t}\n\tif edge.WindowFn != nil {\n\t\tret.WindowFn = encodeWindowFn(edge.WindowFn)\n\t}\n\n\tfor _, in := range edge.Input {\n\t\tkind, err := encodeInputKind(in.Kind)","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/go/pkg/beam/core/runtime/graphx/serialize.go#L31-L67","documentation":"EncodeMultiEdge fails to serialize a DoFn into a protobuf Fn reference (encodeFn on (*graph.Fn)(edge.DoFn)) and wraps the cause as 'bad userfn'. Serialization of user functions requires the fn's package/type info to be resolvable (no closures, valid funcRef). This error surfaces during pipeline graph encoding, typically when submitting a job.","triggerScenarios":"Calling EncodeMultiEdge on a graph.MultiEdge whose DoFn cannot be encoded: anonymous/inner functions or closures, unexported types, values not registered for remote marshaling, or structs with unserializable fields.","commonSituations":"Defining a DoFn as an anonymous struct/inline closure in a Go Beam pipeline submitted to a remote runner; packages renamed/refactored so the recorded funcRef no longer resolves; missing init registration for custom types serialized into fn config.","solutions":["Read the wrapped inner error for the exact encodeFn failure (type not registered, unresolvable path)","Replace closures/anonymous DoFns with named top-level types","Ensure any types used as fn config fields are registered via gob/beam type registration","Verify the package path is importable and matches what the runner expects"],"exampleFix":"// before\nbeam.ParDo(s, &struct{ process func(string) }{...}, in) // anonymous/unserializable\n// after\ntype myFn struct{}\nfunc (myFn) ProcessElement(s string) string { return s }\nbeam.ParDo(s, &myFn{}, in)","handlingStrategy":"validation","validationCode":"if reflect.TypeOf(fn).Kind() == reflect.Struct && !isNamedTopLevel(fn) {\n  return errors.New(\"DoFn must be a named top-level type, not a closure or anonymous struct\")\n}","typeGuard":"func isSerializableDoFn(fn interface{}) bool {\n  t := reflect.TypeOf(fn)\n  return t != nil && t.Name() != \"\" && t.PkgPath() != \"\"\n}","tryCatchPattern":"ref, err := encodeFn((*graph.Fn)(edge.DoFn))\nif err != nil {\n  return fmt.Errorf(\"DoFn %T not serializable (closures/anonymous types unsupported): %w\", edge.DoFn, err)\n}","preventionTips":["Use named exported DoFn types; never closures or anonymous structs","Register custom types used in DoFn config fields","Keep function definitions stable between graph build and submission","Test pipeline serialization locally with mustEncodeMultiEdgeBase64 before remote submission"],"tags":["go","beam","serialization","userfn"],"backgroundTag":"json-marshal-failed","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}