temporalio/temporal · error

component type %s is already registered

Error message

component type %s is already registered

What it means

registerComponent detects that a component with the same Go type was already registered in this Registry (map r.rcByGoType). The registry keeps one entry per Go type, so duplicate registration would silently overwrite state and is rejected. Note this is keyed by Go type, distinct from the FQN/name collision error.

Source

Thrown at chasm/registry.go:276

	for key, value := range rc.contextValues {
		if existingValue, ok := r.rcContextValues[key]; ok {
			return fmt.Errorf("context value key %v registered by component %s conflicts with component %s", key, fqn, existingValue.fqn)
		}
		r.rcContextValues[key] = valueWithFqn{
			v:   value,
			fqn: fqn,
		}
	}

	// rc.goType implements Component interface; therefore, it must be a struct.
	// This check to protect against the interface itself being registered.
	if !(rc.goType.Kind() == reflect.Struct ||
		(rc.goType.Kind() == reflect.Pointer && rc.goType.Elem().Kind() == reflect.Struct)) {
		return fmt.Errorf("component type %s must be struct or pointer to struct", rc.goType.String())
	}
	if _, ok := r.rcByGoType[rc.goType]; ok {
		return fmt.Errorf("component type %s is already registered", rc.goType.String())
	}
	r.warnUnmanagedFields(fqn, rc)

	r.rcByFqn[fqn] = rc
	r.rcByID[id] = rc
	r.rcByGoType[rc.goType] = rc
	return nil
}

func (r *Registry) validate(rc *RegistrableComponent) error {
	if err := r.validateName(rc.componentType); err != nil {
		return err
	}
	return r.validateVisibilityBusinessIDAlias(rc)
}

func (r *Registry) registerTask(
	lib namer,

View on GitHub (pinned to bde624efd1)

Solutions

  1. Remove the duplicate chasm.Register call so the component is registered exactly once.
  2. Guard registration with a sync.Once or package-level init in a single shared package.
  3. If per-test registries are intended, construct a fresh Registry instead of reusing the global one.

Example fix

// before
func init() { chasm.Register[Foo](reg, opts) }
func main() { chasm.Register[Foo](reg, opts) }
// after
func init() { chasm.Register[Foo](reg, opts) }
Defensive patterns

Strategy: validation

Validate before calling

if _, ok := registryLookupComponent(reflect.TypeOf(MyComponentImpl{})); ok {
	return errors.New("component already registered; skip duplicate Register call")
}

Try / catch

if err := chasm.Register[MyComponentImpl](reg, opts); err != nil {
	var dup *DuplicateRegistrationError
	if errors.As(err, &dup) { /* idempotent: skip */ }
	return err
}

Prevention

When it happens

Trigger: Calling chasm.Register twice with the same component Go type, even if other options differ; calling Register in multiple init() functions or test setups that share one Registry; a shared library package whose init registers a component an app also registers.

Common situations: Duplicate init() in main package and imported package; test suite re-registering in a beforeEach; two binaries linking the same registration helper.

Related errors


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