apache/beam · error

grpc.Hook: registered twice

Error message

grpc.Hook: %s registered twice

What it means

RegisterHook stores a gRPC HookFactory by identifier in a global registry and panics if the same identifier is already registered, enforcing that each hook name maps to exactly one factory. This is an init-time invariant check for duplicate hook definitions.

Solutions

  1. Remove the duplicate RegisterHook call or guard it with a sync.Once
  2. Rename one of the conflicting hooks so identifiers are unique
  3. Check init() functions of imported packages for duplicate hook registrations
  4. If intentional re-registration is needed, add an idempotency check before calling RegisterHook

Example fix

// before
func init() { grpcx.RegisterHook("grpc", myFactory) } // panics if already registered
// after
var once sync.Once
func init() { once.Do(func() { grpcx.RegisterHook("grpc", myFactory) }) }
Defensive patterns

Strategy: try-catch

Validate before calling

if _, exists := hookNameTaken(name); exists {
	return fmt.Errorf("hook %s already registered", name)
}

Try / catch

defer func() {
	if r := recover(); r != nil {
		err = fmt.Errorf("hook registration failed: %v", r)
	}
}()

Prevention

When it happens

Trigger: Calling grpcx.RegisterHook("grpc", factory) twice — e.g. the same hook package imported from two paths, or two packages both registering a hook with the identical name in init().

Common situations: Duplicate registration in init() functions across vendored and non-vendored copies of a hook package; two custom hooks accidentally given the same name; test binaries linking both a hook and its test double.

Related errors


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

Appendix: source

Thrown at sdks/go/pkg/beam/util/grpcx/hook.go:47

// is optional; the default behavior will be used if a value is not
// supplied.
type Hook struct {
	// Dialer allows the runner to customize the gRPC dialing behavior.
	Dialer func(context.Context, string, time.Duration) (*grpc.ClientConn, error)
	// TODO(wcn): expose other hooks here.
}

// HookFactory is a function that creates hooks from supplied arguments.
type HookFactory func([]string) Hook

var hookRegistry = make(map[string]HookFactory)

// RegisterHook registers a HookFactory for the
// supplied identifier. It panics if the same identifier is
// registered twice.
func RegisterHook(name string, c HookFactory) {
	if _, exists := hookRegistry[name]; exists {
		panic(fmt.Sprintf("grpc.Hook: %s registered twice", name))
	}
	hookRegistry[name] = c

	hf := func(opts []string) hooks.Hook {
		return hooks.Hook{
			Init: func(ctx context.Context) (context.Context, error) {
				if len(opts) == 0 {
					return ctx, nil
				}

				name, opts := hooks.Decode(opts[0])
				grpcHook := hookRegistry[name](opts)
				if grpcHook.Dialer != nil {
					Dial = grpcHook.Dialer
				}
				return ctx, nil
			},
		}

View on GitHub (pinned to 12126d8942)