temporalio/temporal · error
task %s is already registered
Error message
task %s is already registered
What it means
registerTask found an existing RegistrableTask under the same fully-qualified name (library + task type) in r.rtByFqn. FQNs must be unique across the registry because they name the task in persisted data and lookups. This differs from the Go-type duplicate error — same name, different type still collides.
Source
Thrown at chasm/registry.go:307
}
return r.validateVisibilityBusinessIDAlias(rc)
}
func (r *Registry) registerTask(
lib namer,
rt *RegistrableTask,
) error {
if err := r.validateName(rt.taskType); err != nil {
return err
}
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())View on GitHub (pinned to bde624efd1)
Solutions
- Remove or deduplicate the second chasm.Register call for that task.
- Give the second task a unique taskType name (e.g. prefix with the owning package).
- If the FQN must stay stable for already-persisted data, keep one canonical registration and delete the new one.
Example fix
// before chasm.RegisterTask[CleanupWorker, OrderImpl](reg, "cleanup", opts) chasm.RegisterTask[CleanupWorkerV2, OrderImpl](reg, "cleanup", opts) // after chasm.RegisterTask[CleanupWorker, OrderImpl](reg, "cleanup", opts) chasm.RegisterTask[CleanupWorkerV2, OrderImpl](reg, "cleanup_v2", opts)
Defensive patterns
Strategy: validation
Validate before calling
fqn := libName + "." + taskTypeName
if existingFQNs[fqn] {
return fmt.Errorf("task %s already registered", fqn)
} Try / catch
if err := chasm.RegisterTask[T, C](reg, name, opts); err != nil && strings.Contains(err.Error(), "is already registered") {
return nil // idempotent re-registration
} Prevention
- Prefix task names with their owning package to keep FQNs unique.
- Keep all task registrations in one central bootstrap function.
- Never rename a registered task's name after deployment without a migration plan.
When it happens
Trigger: Registering two tasks whose (library name, task type name) pairs resolve to the same FQN; registering the same task type under two different Go types; re-running registration with a renamed Go type but same name.
Common situations: Renaming a task struct without changing its registered name after a release; two teams independently picking the same task name in the same library; copying registration lines across services sharing a registry.
Related errors
- task type %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/f32ed42fbea6d995.
Report an issue: GitHub.