micro/go-micro · error

subscriber %v.%v takes wrong number of args: %v required sig

Error message

subscriber %v.%v takes wrong number of args: %v required signature %s

What it means

For struct-based subscribers, every exported method is validated as a handler and must take exactly 3 inputs: receiver, context.Context, and the message (subSig func(context.Context, interface{}) error plus the receiver). validateSubscriber iterates typ.NumMethod() and fails on the first method whose NumIn() != 3, including methods you may not have intended as subscribers.

Source

Thrown at server/grpc/subscriber.go:148

		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)
			}
			if method.Type.NumOut() != 1 {
				return fmt.Errorf(
					"subscriber %v.%v has wrong number of outs: %v require signature %s",
					name, method.Name, method.Type.NumOut(), subSig)
			}
			if returnType := method.Type.Out(0); returnType != typeOfError {
				return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
			}
		}
	}

	return nil

View on GitHub (pinned to 24529f1404)

Solutions

  1. Give every exported method on the registered struct the signature (ctx context.Context, msg *T) error.
  2. Move non-subscriber helpers off the struct or make them unexported (lowercase) so NumMethod doesn't see them.
  3. Register a dedicated subscriber struct containing only handler methods, separate from your service struct.
  4. If the handler needs extra config, capture it in the struct's fields (set at construction), not as method parameters.

Example fix

// before
type Handlers struct{}
func (h *Handlers) Handle(ctx context.Context, e *pb.Event, opts ...Option) error { ... }
// after
type Handlers struct{ opts []Option }
func (h *Handlers) Handle(ctx context.Context, e *pb.Event) error { ... }
Defensive patterns

Strategy: validation

Validate before calling

func validSubscriberStruct(h interface{}) error {
	t := reflect.TypeOf(h)
	ctxType := reflect.TypeOf((*context.Context)(nil)).Elem()
	errType := reflect.TypeOf((*error)(nil)).Elem()
	for i := 0; i < t.NumMethod(); i++ {
		m := t.Method(i)
		if m.Type.NumIn() != 3 || !m.Type.In(1).Implements(ctxType) || m.Type.NumOut() != 1 || m.Type.Out(0) != errType {
			return fmt.Errorf("method %s does not match func(ctx context.Context, msg T) error", m.Name)
		}
	}
	return nil
}

Prevention

When it happens

Trigger: Registering a struct via Subscribe(topic, handlerStruct) where any exported method has the wrong arity, e.g. methods with only (msg *T) error (NumIn 2), or helper/cleanup/exported utility methods with extra parameters (NumIn 4+).

Common situations: Adding a context parameter or extra option to one method on the handler struct; leaving exported helper methods (Health, Close, Configure) on the same struct; value-receiver vs pointer-receiver confusion changing the method set; upgrading go-micro and getting stricter validation than before.

Related errors


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