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

  1. Delete the second registration of the same task Go type.
  2. Centralize task registration in one init/bootstrap function.
  3. 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

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


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/50aac765dc353ade. Report an issue: GitHub.