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

  1. Use the concrete task struct (or *struct) as the type parameter.
  2. Verify with reflect.TypeOf(task).Kind() before registering.
  3. 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

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


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