temporalio/temporal · error

task 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.

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.

Appendix: 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)