temporalio/temporal · critical
category id: %v has already been defined as type %v and name
Error message
category id: %v has already been defined as type %v and name %v
What it means
MutableTaskCategoryRegistry.AddCategory registers a task Category keyed by its numeric ID. It deliberately panics (instead of erroring) when the same ID is registered twice, since category registration happens during package init and a duplicate indicates a compile-time-level configuration bug. The existing registration's type and name are reported to help identify the collision.
Source
Thrown at service/history/tasks/task_category_registry.go:45
// you're in a test.
func NewDefaultTaskCategoryRegistry() *MutableTaskCategoryRegistry {
return &MutableTaskCategoryRegistry{
categories: map[int]Category{
CategoryTransfer.ID(): CategoryTransfer,
CategoryTimer.ID(): CategoryTimer,
CategoryVisibility.ID(): CategoryVisibility,
CategoryReplication.ID(): CategoryReplication,
CategoryMemoryTimer.ID(): CategoryMemoryTimer,
CategoryOutbound.ID(): CategoryOutbound,
},
}
}
// AddCategory register a Category with the registry or panics if a Category with the same ID has already been
// registered.
func (r *MutableTaskCategoryRegistry) AddCategory(c Category) {
if category, ok := r.categories[c.id]; ok {
panic(fmt.Sprintf(
"category id: %v has already been defined as type %v and name %v",
c.id,
category.cType,
category.name,
))
}
r.categories[c.id] = c
}
// GetCategoryByID returns a registered Category with the same ID from the registry or false if no such Category exists.
func (r *MutableTaskCategoryRegistry) GetCategoryByID(id int) (Category, bool) {
category, ok := r.categories[id]
return category, ok
}
// GetCategories returns a deep copy of all registered Category objects from the registry.
func (r *MutableTaskCategoryRegistry) GetCategories() map[int]Category {View on GitHub (pinned to bde624efd1)
Solutions
- Allocate a new, unused category ID for the new category; check all RegisterTaskCategory calls in the tasks package for taken IDs.
- Remove the duplicate AddCategory call (often left by a bad merge).
- Search for all categories registered with the reported id and decide which definition is authoritative.
- Coordinate ID allocation across teams/forks since IDs are persisted in task rows and must be globally unique.
Example fix
// before CategoryTransfer = 1 CategoryReplication = 1 // collides // after CategoryTransfer = 1 CategoryReplication = 2
Defensive patterns
Strategy: validation
Validate before calling
if registry.GetCategory(2) != nil {
return errors.New("category id 2 already registered")
}
registry.AddCategory(myCategory) Prevention
- Maintain a single registry of allocated category IDs and pick unused IDs from it
- Grep for RegisterTaskCategory/AddCategory calls when adding a new category
- Never reuse persisted category IDs; they are stored in database task rows
- Resolve merge conflicts around category registration blocks carefully
When it happens
Trigger: Calling AddCategory with a Category whose id collides with one already registered — happens when defining a new task category reusing an existing numeric ID, or registering the same category twice (e.g. duplicated init code).
Common situations: Adding a new category in a fork/patch without allocating a fresh ID; merge conflicts leaving two registration blocks; upstream ID reassignment causing collisions with locally added categories.
Related errors
- dynamicconfig.New*Setting must only be called from static in
- duplicate registration of dynamic config key: %q
- reader with ID %v already exists
- ErrDuplicateRegistration
- component type %s must be struct or pointer to struct
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/6daefc8fc0ad49bb.
Report an issue: GitHub.