temporalio/temporal · error
task type %s is already registered
Error message
task type %s is already registered
What it means
registerTask found that r.rtByGoType already contains a task with this exact Go type — the same task implementation was registered twice. The registry allows only one registration per Go type regardless of the name used.
Source
Thrown at chasm/registry.go:319
fqn, id, err := rt.registerToLibrary(lib)
if err != nil {
return err
}
if _, ok := r.rtByFqn[fqn]; ok {
return fmt.Errorf("task %s is already registered", fqn)
}
if existingTask, ok := r.rtByID[id]; ok {
return fmt.Errorf("task type ID %d collision between %s and %s", id, fqn, existingTask.fqType())
}
if !(rt.goType.Kind() == reflect.Struct ||
(rt.goType.Kind() == reflect.Pointer && rt.goType.Elem().Kind() == reflect.Struct)) {
return fmt.Errorf("task type %s must be struct or pointer to struct", rt.goType.String())
}
if _, ok := r.rtByGoType[rt.goType]; ok {
return fmt.Errorf("task type %s is already registered", rt.goType.String())
}
if !(rt.componentGoType.Kind() == reflect.Interface ||
(rt.componentGoType.Kind() == reflect.Struct ||
(rt.componentGoType.Kind() == reflect.Pointer && rt.componentGoType.Elem().Kind() == reflect.Struct)) &&
rt.componentGoType.AssignableTo(reflect.TypeFor[Component]())) {
return fmt.Errorf("component type %s must be and interface or struct that implements Component interface", rt.componentGoType.String())
}
r.rtByFqn[fqn] = rt
r.rtByID[id] = rt
r.rtByGoType[rt.goType] = rt
return nil
}
func (r *Registry) validateName(n string) error {
if n == "" {
return errors.New("name must not be empty")
}View on GitHub (pinned to bde624efd1)
Solutions
- Delete the second registration of the same task Go type.
- Centralize task registration in one init/bootstrap function.
- Use a fresh Registry instance for isolated test cases instead of re-registering into a shared one.
Example fix
// before chasm.RegisterTask[EmailTask, OrderImpl](reg, "email", opts) chasm.RegisterTask[EmailTask, OrderImpl](reg, "email2", opts) // after chasm.RegisterTask[EmailTask, OrderImpl](reg, "email", opts)
Defensive patterns
Strategy: validation
Validate before calling
if _, ok := registryLookupTask(reflect.TypeOf(MyTask{})); ok {
return errors.New("task already registered; skip duplicate RegisterTask call")
} Try / catch
if err := chasm.RegisterTask[MyTask, C](reg, name, opts); err != nil && strings.Contains(err.Error(), "is already registered") {
return nil
} Prevention
- Single central init/bootstrap for all task registrations.
- Avoid registering tasks in both library and application packages.
- Use fresh Registries per test case to isolate registration state.
When it happens
Trigger: Calling chasm.RegisterTask twice with the same task Go type (even with different names/options); duplicate init() paths registering the same task; test setup re-registering tasks on a shared registry.
Common situations: Task registered in both a library's init and the application's bootstrap; re-running a bootstrap function during tests; accidental duplicated registration line after a merge.
Related errors
- task %s is already registered
- component type %s is already registered
- task type ID %d collision between %s and %s
- task type %s must be struct or pointer to struct
- component type %s must be and interface or struct that imple
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/50aac765dc353ade.
Report an issue: GitHub.