apache/beam · critical

Invalid signature for {{$upperName}}

Error message

Invalid signature for {{$upperName}}

What it means

This panic is raised by the generated buildStartBundleWrapper/buildFinishBundleWrapper functions in Apache Beam Go's register package (rendered from register.tmpl). The DoFn has a StartBundle or FinishBundle method (found via reflect.MethodByName), but its signature's number of outputs (or number of inputs) doesn't fall into any supported case in the generated switch — only 0 or 1 outputs and up to startFinishBundleMaxIn inputs are supported. The library panics because it cannot build a wrapper for an unsupported method shape.

Source

Thrown at sdks/go/pkg/beam/register/register.tmpl:37

	{{$lowerName}}Out := -1
	{{$lowerName}}Method := reflect.ValueOf(doFn).MethodByName("{{$upperName}}")
	if !{{$lowerName}}Method.IsValid() {
        return nil
    }
    {{$lowerName}}In = {{$lowerName}}Method.Type().NumIn()
    {{$lowerName}}Out = {{$lowerName}}Method.Type().NumOut()
    switch {
    {{range $funcIn := upto $startFinishBundleMaxIn}}
    case {{$lowerName}}In == {{$funcIn}}:
            switch { {{range $funcOut := upto 2}}{{$possibleCombos := (possibleBundleLifecycleParameterCombos $funcIn $numParams)}}{{if $possibleCombos}}
            case {{$lowerName}}Out == {{$funcOut}}:
    {{$first := true}}{{range $funcCombo := $possibleCombos}}{{if $first}}{{$first = false}}                {{else}} else {{end}}if _, ok := doFn.({{$lowerName}}{{$funcIn}}x{{$funcOut}}{{if (or $funcIn $funcOut)}}[{{(join $funcCombo ", ")}}{{if $funcOut}}{{if $funcIn}}, {{end}}error{{end}}]{{end}}); ok {
                    return register{{$upperName}}{{$funcIn}}x{{$funcOut}}FuncAndMakeStructWrapper{{if (or $funcIn $funcOut)}}[{{(join $funcCombo ", ")}}{{if $funcOut}}{{if $funcIn}}, {{end}}error{{end}}]{{end}}()
                } {{end}}{{end}}else {
                    panic("Unable to infer the types of {{$upperName}}")
                }{{end}}
            default:
                panic("Invalid signature for {{$upperName}}")
            }
    {{end}}
    default:
        panic("Invalid signature for {{$upperName}}")
    }
}
{{end}}{{define "BuildWrapper_SetupTeardown"}}
{{$lowerName := "unknown"}}{{$upperName := "unknown"}}{{if (eq .func "setup")}}{{$lowerName = "setup"}}{{$upperName = "Setup"}}{{end}}{{if (eq .func "teardown")}}{{$lowerName = "teardown"}}{{$upperName = "Teardown"}}{{end}}
func build{{$upperName}}Wrapper(doFn any) func(any) reflectx.Func {
    if _, ok := doFn.({{$lowerName}}0x0); ok {
        {{$lowerName}}Caller := func(fn any) reflectx.Func {
            f := fn.(func())
            return &caller0x0{fn: f}
        }
        reflectx.RegisterFunc(reflect.TypeOf((*func())(nil)).Elem(), {{$lowerName}}Caller)

        return func(fn any) reflectx.Func {
            return reflectx.MakeFunc(func() {

View on GitHub (pinned to 12126d8942)

Solutions

  1. Change StartBundle/FinishBundle to return at most one value (either nothing or error): e.g. func (fn *MyFn) FinishBundle() error.
  2. Reduce lifecycle method parameters to the supported set (none, or context.Context and/or the accumulator types matching ProcessElement combos).
  3. Move extra logic/returns out of the lifecycle method into ProcessElement or struct state.
  4. Check the Beam version's supported signature table in sdks/go/pkg/beam/core/graph/coder or register docs and conform to it.
  5. If a method genuinely isn't a lifecycle hook, rename it so reflect.MethodByName doesn't pick it up.

Example fix

// before: two outputs unsupported
type MyFn struct{}
func (fn *MyFn) StartBundle(ctx context.Context) (bool, error) { return true, nil }

// after: at most one output
type MyFn struct{}
func (fn *MyFn) StartBundle(ctx context.Context) error { return nil }
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check outputs/inputs of lifecycle methods before registration:
func checkSig(fn any, method string, maxIn int) error {
    m := reflect.ValueOf(fn).MethodByName(method)
    if !m.IsValid() {
        return nil
    }
    t := m.Type()
    if t.NumIn() > maxIn {
        return fmt.Errorf("%s: %d inputs exceeds max %d", method, t.NumIn(), maxIn)
    }
    if t.NumOut() > 1 {
        return fmt.Errorf("%s: %d outputs exceeds max 1", method, t.NumOut())
    }
    return nil
}

Type guard

func hasSupportedFinishBundle(fn any) bool {
    _, ok := fn.(interface{ FinishBundle() })
    _, okE := fn.(interface{ FinishBundle() error })
    return ok || okE
}

Try / catch

func registerSafe(fn any) {
    defer func() {
        if r := recover(); r != nil {
            log.Fatalf("unsupported DoFn signature (fix StartBundle/FinishBundle/Setup/Teardown shape): %v", r)
        }
    }()
    register.DoFn2x1(fn)
}

Prevention

When it happens

Trigger: Registering a DoFn whose StartBundle/FinishBundle method returns 2+ values (e.g. func(context.Context) (T0, T1, error)), or takes more inputs than the configured maximum (startFinishBundleMaxIn, driven by ProcessElement max params); also the outer default case when NumIn exceeds all enumerated cases.

Common situations: Porting DoFns from other Beam SDKs that allow richer lifecycle signatures; adding extra return values like (bool, error) for control flow; auto-generated DoFn wrappers with bloated signatures; newer Beam versions raising/lowering the supported arity so previously-working code panics after upgrade.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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