temporalio/temporal · error
task type %s must be struct or pointer to struct
Error message
task type %s must be struct or pointer to struct
What it means
registerTask rejects task implementations whose reflect.Type is not a struct or pointer to a struct. Like components, tasks must be backed by concrete structs; registering an interface or other kind is unsupported and usually means the interface type was passed by mistake.
Source
Thrown at chasm/registry.go:316
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
r.rtByGoType[rt.goType] = rt
return nil
}
func (r *Registry) validateName(n string) error {View on GitHub (pinned to bde624efd1)
Solutions
- Use the concrete task struct (or *struct) as the type parameter.
- Verify with reflect.TypeOf(task).Kind() before registering.
- If generics got in the way, constrain the type parameter to struct types at the call site.
Example fix
// before chasm.RegisterTask[TaskInterface, OrderImpl](reg, "x", opts) // after chasm.RegisterTask[TaskImpl, OrderImpl](reg, "x", opts)
Defensive patterns
Strategy: validation
Validate before calling
t := reflect.TypeOf((*MyTask)(nil)).Elem()
if !(t.Kind() == reflect.Struct || (t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Struct)) {
return fmt.Errorf("task type %s must be struct or pointer to struct", t)
} Type guard
func isTaskStruct(t reflect.Type) bool {
return t != nil && (t.Kind() == reflect.Struct || (t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Struct))
} Prevention
- Pass the concrete task struct, not its interface, to chasm.RegisterTask.
- Constrain generic helpers with [T any, PT interface{ *T; chasm.Task }] style bounds.
- Add a compile-time assertion for the task type where feasible.
When it happens
Trigger: Calling chasm.RegisterTask with the task interface type as the type parameter, or with a non-struct kind (map, slice, func).
Common situations: Passing the interface name instead of the implementation struct; generic wrapper that accidentally instantiates with an interface; refactoring a struct into an interface.
Related errors
- component type %s must be struct or pointer to struct
- task %s is already registered
- task type ID %d collision between %s and %s
- task type %s is already registered
- component type %s must be and interface or struct that imple
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/577a3fe83fb397eb.
Report an issue: GitHub.