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
- Remove the duplicate RegisterHook call or guard it with a sync.Once
- Rename one of the conflicting hooks so identifiers are unique
- Check init() functions of imported packages for duplicate hook registrations
- 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
- Register hooks in a single init() guarded by sync.Once
- Use unique, package-qualified hook names
- Check import graphs for duplicate copies of the same hook package
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
- EnableHook: not registered
- empty port
- EnableHook: can't enable hook
- Init hooks have already run. Register hook during init()…
- unable to dial sdk worker pool
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)