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

  1. Remove or deduplicate the second chasm.Register call for that task.
  2. Give the second task a unique taskType name (e.g. prefix with the owning package).
  3. 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

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


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