temporalio/temporal · error
component type %s must be struct or pointer to struct
Error message
component type %s must be struct or pointer to struct
What it means
chasm.Registry.registerComponent rejects component implementations whose reflect.Type is not a struct or a pointer to a struct. The Component interface is backed by Go structs, so registering the interface type itself or any other kind (map, slice, int, etc.) cannot be managed by the framework. This guard exists to prevent accidentally registering the interface type when the intent was a concrete implementation.
Source
Thrown at chasm/registry.go:273
if existingComponent, ok := r.rcByID[id]; ok {
return fmt.Errorf("component ID %d collision between %s and %s", id, fqn, existingComponent.fqType())
}
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)
}View on GitHub (pinned to bde624efd1)
Solutions
- Register the concrete struct type: chasm.Register[MyComponentImpl](registry, ...).
- If *MyComponentImpl is used elsewhere, either kind works — but never the interface type.
- Print reflect.TypeOf to confirm the registered type is a struct or *struct.
Example fix
// before chasm.Register[OrderComponent](reg, opts) // after chasm.Register[OrderComponentImpl](reg, opts)
Defensive patterns
Strategy: validation
Validate before calling
t := reflect.TypeOf((*MyComponent)(nil)).Elem()
if t.Kind() == reflect.Interface ||
!(t.Kind() == reflect.Struct || (t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Struct)) {
return fmt.Errorf("%s must be struct or pointer to struct", t)
} Type guard
func isStructOrStructPtr(t reflect.Type) bool {
return t != nil && (t.Kind() == reflect.Struct || (t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Struct))
} Prevention
- Always pass the concrete struct type, never the interface, as the type parameter to chasm.Register.
- Add a compile-time check: var _ chasm.Component = (*MyComponentImpl)(nil).
- Register components in a single package-level init per service.
When it happens
Trigger: Calling chasm.Register (which calls registerComponent) with a generic parameter that is an interface type, e.g. Register[SomeComponent](...), or a non-struct type such as a named map/slice/func type.
Common situations: Developer passes the interface name instead of the concrete struct; refactor renamed a struct into an interface; copy-pasted registration code where the type parameter was swapped; registering a type alias pointing to a non-struct kind.
Related errors
- component type %s is already registered
- task type %s must be struct or pointer to struct
- ErrDuplicateRegistration
- task %s is already registered
- task type ID %d collision between %s and %s
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/bb83f3fa922c28fe.
Report an issue: GitHub.