temporalio/temporal · error

task type ID %d collision between %s and %s

Error message

task type ID %d collision between %s and %s

What it means

Two different tasks hashed/derived the same numeric task type ID: r.rtByID already holds a task for this ID. IDs are derived from the task's FQN and are used as compact identifiers, so a collision means two tasks would be indistinguishable in encoded form. The error names both colliding FQNs.

Source

Thrown at chasm/registry.go:311

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())
	}

	r.rtByFqn[fqn] = rt
	r.rtByID[id] = rt

View on GitHub (pinned to bde624efd1)

Solutions

  1. Rename one of the two conflicting task types so its derived ID changes.
  2. Check the two FQNs in the message and pick a new, distinct name for the newer task.
  3. If the ID space is shared across services, coordinate task naming conventions to avoid overlaps.

Example fix

// before
chasm.RegisterTask[T1, C](reg, "order_close", opts)
chasm.RegisterTask[T2, C](reg, "orderclose", opts) // same derived ID
// after
chasm.RegisterTask[T1, C](reg, "order_close", opts)
chasm.RegisterTask[T2, C](reg, "order_close_v2", opts)
Defensive patterns

Strategy: validation

Validate before calling

// Maintain a set of assigned task type IDs at build/bootstrap time and fail fast on duplicates:
if seenIDs[id] {
	return fmt.Errorf("task type ID %d already used", id)
}

Prevention

When it happens

Trigger: registering task A, then task B whose derived ID equals A's (ID derived from name); a version change that renamed one task so its ID now matches another.

Common situations: Large libraries where hash-derived IDs collide; renaming tasks during a refactor causing two names to map to one ID; registering tasks from multiple libraries that independently picked names producing the same ID.

Related errors


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