temporalio/temporal · error
ErrDuplicateRegistration
ErrDuplicateRegistration
Error message
%w: command handler for %v: %v
What it means
The CHASM workflow Registry.Register rejects a Library whose command handler map contains a command type already registered by a previously registered library. The error wraps ErrDuplicateRegistration and names the conflicting command type plus the existing handler. Registration is expected to happen once, single-threaded, at process init.
Source
Thrown at chasm/lib/workflow/registry.go:47
}
// NewRegistry creates a new [Registry].
func NewRegistry() *Registry {
return &Registry{
commandHandlers: make(map[enumspb.CommandType]CommandHandler),
eventDefinitions: make(map[enumspb.EventType]EventDefinition),
eventDefinitionsByGoType: make(map[reflect.Type]EventDefinition),
}
}
// Register registers all command handlers and event definitions from a [Library].
// Returns an [ErrDuplicateRegistration] if a handler or definition is already registered.
// All registration is expected to happen in a single thread on process initialization.
func (r *Registry) Register(lib Library) error {
for t, handler := range lib.CommandHandlers() {
if existing, ok := r.commandHandlers[t]; ok {
return fmt.Errorf("%w: command handler for %v: %v", ErrDuplicateRegistration, t, existing)
}
r.commandHandlers[t] = handler
}
for _, def := range lib.EventDefinitions() {
if existing, ok := r.eventDefinitions[def.Type()]; ok {
return fmt.Errorf("%w: event handler for %v: %v", ErrDuplicateRegistration, def.Type(), existing)
}
goType := reflect.TypeOf(def)
for goType.Kind() == reflect.Pointer {
goType = goType.Elem()
}
if existing, ok := r.eventDefinitionsByGoType[goType]; ok {
return fmt.Errorf("%w: event definition for Go type %v: %v", ErrDuplicateRegistration, goType, existing)
}
r.eventDefinitions[def.Type()] = def
r.eventDefinitionsByGoType[goType] = def
}
return nil
}View on GitHub (pinned to bde624efd1)
Solutions
- Find the duplicate: the message names the command type and existing handler — locate both libraries registering it
- Remove one registration or consolidate the two command handlers into a single library
- Guard registration with a sync.Once or check-and-skip so init paths that may run twice register only once
- If intentional replacement is needed, create a fresh Registry instead of reusing one (no overwrite API exists)
Example fix
// before registry.Register(libA) // registers command type Foo registry.Register(libB) // also registers command type Foo -> duplicate // after registry.Register(libA) registry.Register(libBWithoutFoo) // libB no longer defines Foo handler
Defensive patterns
Strategy: validation
Validate before calling
// Guard against double registration before calling Register
var once sync.Once
func registerOnce(r *chasmworkflow.Registry, lib Library) {
once.Do(func() { _ = r.Register(lib) })
} Try / catch
// Go
if err := registry.Register(lib); err != nil {
if errors.Is(err, ErrDuplicateRegistration) {
logger.Warn("library already registered; skipping", "err", err)
return nil
}
return err
} Prevention
- Register all libraries exactly once at process init, single-threaded
- Keep a central registration function; never call Register from library init()s
- Audit bundled libraries for overlapping command types before adding a new one
- On duplicate, treat it as a build/packaging error, not a runtime retry case
When it happens
Trigger: Calling Registry.Register(lib) where lib.CommandHandlers() returns a handler for a command type t that r.commandHandlers already holds — typically by registering two libraries that both define handlers for the same command type, or re-registering the same library twice.
Common situations: Two plugins/libraries bundled into the same binary both register the same command type; an init() or registration path runs twice (e.g. library imported in two places, or Register called in both init and main); copy-pasted library code kept the same command type.
Related errors
- errInvalidWorkflowExecutionTimeoutSeconds
- errInvalidWorkflowRunTimeoutSeconds
- errInvalidWorkflowTaskTimeoutSeconds
- component type %s must be struct or pointer to struct
- component type %s is already registered
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/b19c398e0bf54e0e.
Report an issue: GitHub.