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] = rtView on GitHub (pinned to bde624efd1)
Solutions
- Rename one of the two conflicting task types so its derived ID changes.
- Check the two FQNs in the message and pick a new, distinct name for the newer task.
- 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
- Choose distinct, descriptive task names — ID is derived from the FQN.
- Run a startup self-check/test that registers all tasks and asserts no collision.
- Coordinate task naming across teams sharing one registry/library.
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
- task %s is already registered
- task type %s must be struct or pointer to struct
- task type %s is already registered
- component type %s must be and interface or struct that imple
- ErrDuplicateRegistration
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/7292ec3ec5c4a5fe.
Report an issue: GitHub.