hyperledger/fabric · error

receiver %T.%s does not return an error as its second return

Error message

receiver %T.%s does not return an error as its second return value

What it means

Dispatch requires the second of the method's two return values to implement the error interface so failures can be propagated to the caller. This error is thrown when the second return type does not implement error.

Source

Thrown at core/dispatcher/dispatcher.go:52

	if method.Type().NumIn() != 1 {
		return nil, errors.Errorf("receiver %T.%s has %d parameters but expected 1", receiver, methodName, method.Type().NumIn())
	}

	inputType := method.Type().In(0)
	if inputType.Kind() != reflect.Pointer {
		return nil, errors.Errorf("receiver %T.%s does not accept a pointer as its argument", receiver, methodName)
	}

	if method.Type().NumOut() != 2 {
		return nil, errors.Errorf("receiver %T.%s returns %d values but expected 2", receiver, methodName, method.Type().NumOut())
	}

	if !method.Type().Out(0).Implements(reflect.TypeFor[proto.Message]()) {
		return nil, errors.Errorf("receiver %T.%s does not return a an implementor of proto.Message as its first return value", receiver, methodName)
	}

	if !method.Type().Out(1).Implements(reflect.TypeFor[error]()) {
		return nil, errors.Errorf("receiver %T.%s does not return an error as its second return value", receiver, methodName)
	}

	inputValue := reflect.New(inputType.Elem())
	inputMsg, ok := inputValue.Interface().(proto.Message)
	if !ok {
		return nil, errors.Errorf("receiver %T.%s does not accept a proto.Message as its argument, it is '%T'", receiver, methodName, inputValue.Interface())
	}

	err := d.Protobuf.Unmarshal(inputBytes, inputMsg)
	if err != nil {
		return nil, errors.WithMessagef(err, "could not decode input arg for %T.%s", receiver, methodName)
	}

	outputVals := method.Call([]reflect.Value{inputValue})

	if !outputVals[1].IsNil() {
		return nil, outputVals[1].Interface().(error)
	}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Change the second return value to the standard error interface: func (s *Svc) Do(req *pb.Req) (*pb.Resp, error).
  2. If a custom error type is kept, add an Error() string method so it implements error.
  3. Move custom failure details into the response proto message and return a plain error instead.

Example fix

// before
func (s *Svc) Do(req *pb.Req) (*pb.Resp, *FailureInfo) { ... }

// after
func (s *Svc) Do(req *pb.Req) (*pb.Resp, error) {
    ...
    if failed {
        return nil, fmt.Errorf("failed: %v", info)
    }
    return resp, nil
}
Defensive patterns

Strategy: validation

Validate before calling

func validateSecondReturnIsError(receiver any, methodName string) error {
    m := reflect.ValueOf(receiver).MethodByName(methodName)
    if !m.IsValid() || m.Type().NumOut() != 2 {
        return fmt.Errorf("method %s missing or wrong shape", methodName)
    }
    if !m.Type().Out(1).Implements(reflect.TypeFor[error]()) {
        return fmt.Errorf("method %s second return does not implement error", methodName)
    }
    return nil
}

Type guard

func secondReturnIsError(receiver any, methodName string) bool {
    m := reflect.ValueOf(receiver).MethodByName(methodName)
    return m.IsValid() && m.Type().NumOut() >= 2 &&
        m.Type().Out(1).Implements(reflect.TypeFor[error]())
}

Prevention

When it happens

Trigger: Calling Dispatch (via Invoke) on a method like func (s *Svc) Do(req *pb.Req) (*pb.Resp, string) or (*pb.Resp, *MyErrStruct) where the second return type lacks an Error() string method.

Common situations: Handlers returning custom error structs that do not implement error; returning a status value or error code string directly; accidental type or order swaps after refactoring.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/b837075a851d3d7d. Report an issue: GitHub.