micro/go-micro · error

subscriber %v returns %v not error

Error message

subscriber %v returns %v not error

What it means

Beyond returning exactly one value, a subscriber's single return must be the built-in error interface type. validateSubscriber compares typ.Out(0) against typeOfError (reflect.TypeOf((*error)(nil)).Elem()) and rejects anything else, because the dispatcher type-asserts the first return value to error (returnValues[0].Interface().(error)).

Source

Thrown at server/grpc/subscriber.go:135

	var argType reflect.Type

	if typ.Kind() == reflect.Func {
		name := "Func"
		switch typ.NumIn() {
		case 2:
			argType = typ.In(1)
		default:
			return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
		}
		if !isExportedOrBuiltinType(argType) {
			return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
		}
		if typ.NumOut() != 1 {
			return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
				name, typ.NumOut(), subSig)
		}
		if returnType := typ.Out(0); returnType != typeOfError {
			return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
		}
	} else {
		hdlr := reflect.ValueOf(sub.Subscriber())
		name := reflect.Indirect(hdlr).Type().Name()

		for m := 0; m < typ.NumMethod(); m++ {
			method := typ.Method(m)

			switch method.Type.NumIn() {
			case 3:
				argType = method.Type.In(2)
			default:
				return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
					name, method.Name, method.Type.NumIn(), subSig)
			}

			if !isExportedOrBuiltinType(argType) {
				return fmt.Errorf("%v argument type not exported: %v", name, argType)

View on GitHub (pinned to 24529f1404)

Solutions

  1. Change the return to the standard error interface: ... error.
  2. Wrap custom error types in error on return (return myErr where myErr implements error, or fmt.Errorf with %w).
  3. Return nil explicitly when there is no failure.
  4. If you need rich failure info, attach it to a custom error type that satisfies the error interface rather than changing the return type.

Example fix

// before
func(ctx context.Context, e *pb.Event) *EventError { ... }
// after
func(ctx context.Context, e *pb.Event) error { ...; return &EventError{...} }
Defensive patterns

Strategy: validation

Validate before calling

func returnsStdError(fn interface{}) bool {
	t := reflect.TypeOf(fn)
	if t == nil || t.Kind() == reflect.Func && t.NumOut() != 1 { return false }
	return t.NumOut() == 1 && t.Out(0) == reflect.TypeOf((*error)(nil)).Elem()
}

Prevention

When it happens

Trigger: Registering a subscriber whose sole return is a concrete type or other interface, e.g. func(ctx context.Context, e *T) *MyError, or returns a custom error interface, string, or bool instead of error.

Common situations: Handlers written with a project-specific error type for consistency with internal APIs; refactoring that replaced error with a wrapped result type; porting handlers from frameworks that allow arbitrary returns.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/609b5aab0fec966e. Report an issue: GitHub.