diff --git a/.travis.yml b/.travis.yml
index 715b9cb8..1c6f6392 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,8 +14,6 @@ env:
     - CHECK_TYPE="test"
     - INSTALL_TYPE="pip"
     - INSTALL_DEPENDS="pip setuptools"
-    - PIP_ARGS=""
-
   matrix:
     - INSTALL_TYPE="install"
     - INSTALL_TYPE="develop"
@@ -28,14 +26,9 @@ env:
 # This should only fail with pip 10.0.1 with URL requirements (e.g., "package @ URL")
 # Useful for testing un-released upstream fixes
 matrix:
-  include:
-  - python: "3.8-dev"
-    env: INSTALL_TYPE="pip" PIP_ARGS="--pre"
   allow_failures:
   - python: 3.7
     env: INSTALL_DEPENDS="pip==10.0.1 setuptools==30.3.0"
-  - python: "3.8-dev"
-    env: INSTALL_TYPE="pip" PIP_ARGS="--pre"
 
 
 before_install:
@@ -46,7 +39,7 @@ install:
   - |
     if [ "$CHECK_TYPE" = "test" ]; then
         if [ "$INSTALL_TYPE" = "pip" ]; then
-            pip install "$PIP_ARGS" .
+            pip install .
         elif [ "$INSTALL_TYPE" = "install" ]; then
             python setup.py install
         elif [ "$INSTALL_TYPE" = "develop" ]; then
@@ -85,7 +78,7 @@ before_script:
 script:
   - |
     if [ "$CHECK_TYPE" = "test" ]; then
-       pytest -vs -n auto --cov pydra --cov-config .coveragerc --cov-report xml:cov.xml --doctest-modules pydra
+        py.test -vs -n auto --cov pydra --cov-config .coveragerc --cov-report xml:cov.xml --doctest-modules pydra
     elif [ "$CHECK_TYPE" = "style" ]; then
         black pydra setup.py
     fi
diff --git a/pydra/engine/task.py b/pydra/engine/task.py
index 315c72e7..d6bcf030 100644
--- a/pydra/engine/task.py
+++ b/pydra/engine/task.py
@@ -97,29 +97,32 @@ class FunctionTask(TaskBase):
                 )
             else:
                 return_info = func.__annotations__["return"]
-                if hasattr(return_info, "__name__"):
+                if hasattr(return_info, "__name__") and hasattr(
+                    return_info, "__annotations__"
+                ):
                     output_spec = SpecInfo(
                         name=return_info.__name__,
                         fields=list(return_info.__annotations__.items()),
                         bases=(BaseSpec,),
                     )
                 # Objects like int, float, list, tuple, and dict do not have __name__ attribute.
+                elif hasattr(return_info, "__annotations__"):
+                    output_spec = SpecInfo(
+                        name="Output",
+                        fields=list(return_info.__annotations__.items()),
+                        bases=(BaseSpec,),
+                    )
                 else:
-                    if hasattr(return_info, "__annotations__"):
-                        output_spec = SpecInfo(
-                            name="Output",
-                            fields=list(return_info.__annotations__.items()),
-                            bases=(BaseSpec,),
-                        )
-                    else:
-                        output_spec = SpecInfo(
-                            name="Output",
-                            fields=[
-                                ("out{}".format(n + 1), t)
-                                for n, t in enumerate(return_info)
-                            ],
-                            bases=(BaseSpec,),
-                        )
+                    if not isinstance(return_info, tuple):
+                        return_info = (return_info,)
+                    output_spec = SpecInfo(
+                        name="Output",
+                        fields=[
+                            ("out{}".format(n + 1), t)
+                            for n, t in enumerate(return_info)
+                        ],
+                        bases=(BaseSpec,),
+                    )
         elif "return" in func.__annotations__:
             raise NotImplementedError("Branch not implemented")
         self.output_spec = output_spec
@@ -129,9 +132,16 @@ class FunctionTask(TaskBase):
         del inputs["_func"]
         self.output_ = None
         output = cp.loads(self.inputs._func)(**inputs)
-        if not isinstance(output, tuple):
-            output = (output,)
-        self.output_ = list(output)
+        if len(self.output_spec.fields) > 1:
+            if len(self.output_spec.fields) == len(output):
+                self.output_ = list(output)
+            else:
+                raise Exception(
+                    f"expected {len(self.output_spec.fields)} elements, "
+                    f"but {len(output)} were returned"
+                )
+        else:  # if only one element in the fields, everything should be returned together
+            self.output_ = output
 
     def _list_outputs(self):
         return self.output_

functions with multiple values in return statement
If default `out` for output is used (without defining `output_names` - will be in pr) and a function returns two values, only one value is saved. See:

```
@pydra.to_task
def add_vector(x1, y1, x2, y2):
    return (x1 + x2, y1 + y2)

task5 = add_vector(name="add_vect", 
                   x1=[10, 20], y1=[1, 2], x2=[10, 20, 30], y2=[10, 20, 30])
task5.split(splitter=[("x1", "y1"), ("x2", "y2")])
task5(plugin="cf")
task5.result()
```
diff --git a/pydra/engine/tests/test_task.py b/pydra/engine/tests/test_task.py
index 04edf590..e73fa165 100644
--- a/pydra/engine/tests/test_task.py
+++ b/pydra/engine/tests/test_task.py
@@ -73,7 +73,108 @@ def test_annotated_func():
     ]
 
 
+def test_annotated_func_multreturn():
+    """function has two elements in the return statement"""
+
+    @to_task
+    def testfunc(
+        a: float
+    ) -> ty.NamedTuple("Output", [("fractional", float), ("integer", int)]):
+        import math
+
+        return math.modf(a)
+
+    funky = testfunc(a=3.5)
+    assert hasattr(funky.inputs, "a")
+    assert hasattr(funky.inputs, "_func")
+    assert getattr(funky.inputs, "a") == 3.5
+    assert getattr(funky.inputs, "_func") is not None
+    assert set(funky.output_names) == set(["fractional", "integer"])
+    assert funky.__class__.__name__ + "_" + funky.inputs.hash == funky.checksum
+
+    result = funky()
+    assert os.path.exists(funky.cache_dir / funky.checksum / "_result.pklz")
+    assert hasattr(result, "output")
+    assert hasattr(result.output, "fractional")
+    assert result.output.fractional == 0.5
+    assert hasattr(result.output, "integer")
+    assert result.output.integer == 3
+
+    help = funky.help(returnhelp=True)
+    assert help == [
+        "Help for FunctionTask",
+        "Input Parameters:",
+        "- a: float",
+        "- _func: str",
+        "Output Parameters:",
+        "- fractional: float",
+        "- integer: int",
+    ]
+
+
+def test_annotated_func_multreturn_exception():
+    """function has two elements in the return statement,
+        but three element provided in the spec - should raise an error
+    """
+
+    @to_task
+    def testfunc(
+        a: float
+    ) -> ty.NamedTuple(
+        "Output", [("fractional", float), ("integer", int), ("whoknows", int)]
+    ):
+        import math
+
+        return math.modf(a)
+
+    funky = testfunc(a=3.5)
+    with pytest.raises(Exception) as excinfo:
+        funky()
+    assert "expected 3 elements" in str(excinfo.value)
+
+
 def test_halfannotated_func():
+    @to_task
+    def testfunc(a, b) -> int:
+        return a + b
+
+    funky = testfunc(a=10, b=20)
+    assert hasattr(funky.inputs, "a")
+    assert hasattr(funky.inputs, "b")
+    assert hasattr(funky.inputs, "_func")
+    assert getattr(funky.inputs, "a") == 10
+    assert getattr(funky.inputs, "b") == 20
+    assert getattr(funky.inputs, "_func") is not None
+    assert set(funky.output_names) == set(["out1"])
+    assert funky.__class__.__name__ + "_" + funky.inputs.hash == funky.checksum
+
+    result = funky()
+    assert hasattr(result, "output")
+    assert hasattr(result.output, "out1")
+    assert result.output.out1 == 30
+
+    assert os.path.exists(funky.cache_dir / funky.checksum / "_result.pklz")
+
+    funky.result()  # should not recompute
+    funky.inputs.a = 11
+    assert funky.result() is None
+    funky()
+    result = funky.result()
+    assert result.output.out1 == 31
+    help = funky.help(returnhelp=True)
+
+    assert help == [
+        "Help for FunctionTask",
+        "Input Parameters:",
+        "- a: _empty",
+        "- b: _empty",
+        "- _func: str",
+        "Output Parameters:",
+        "- out1: int",
+    ]
+
+
+def test_halfannotated_func_multreturn():
     @to_task
     def testfunc(a, b) -> (int, int):
         return a + 1, b + 1
@@ -131,6 +232,26 @@ def test_notannotated_func():
     assert result.output.out == 20.2
 
 
+def test_notannotated_func_multreturn():
+    """ no annotation and multiple values are returned
+        all elements should be returned as a tuple ans set to "out"
+    """
+
+    @to_task
+    def no_annots(c, d):
+        return c + d, c - d
+
+    natask = no_annots(c=17, d=3.2)
+    assert hasattr(natask.inputs, "c")
+    assert hasattr(natask.inputs, "d")
+    assert hasattr(natask.inputs, "_func")
+
+    result = natask._run()
+    assert hasattr(result, "output")
+    assert hasattr(result.output, "out")
+    assert result.output.out == (20.2, 13.8)
+
+
 def test_exception_func():
     @to_task
     def raise_exception(c, d):
@@ -140,16 +261,31 @@ def test_exception_func():
     assert pytest.raises(Exception, bad_funk)
 
 
-@pytest.mark.xfail(reason="errors from cloudpickle")
-def test_audit(tmpdir):
+def test_audit_prov(tmpdir):
     @to_task
     def testfunc(a: int, b: float = 0.1) -> ty.NamedTuple("Output", [("out", float)]):
         return a + b
 
-    funky = testfunc(a=1, audit_flags=AuditFlag.PROV, messengers=PrintMessenger())
+    funky = testfunc(a=1, audit_flags=AuditFlag.PROV, messengers=FileMessenger())
     funky.cache_dir = tmpdir
     funky()
 
+    funky = testfunc(a=2, audit_flags=AuditFlag.PROV, messengers=FileMessenger())
+    message_path = tmpdir / funky.checksum / "messages"
+    funky.cache_dir = tmpdir
+    funky.messenger_args = dict(message_dir=message_path)
+    funky()
+
+    collect_messages(tmpdir / funky.checksum, message_path, ld_op="compact")
+    assert (tmpdir / funky.checksum / "messages.jsonld").exists()
+
+
+@pytest.mark.xfail(reason="errors from cloudpickle")
+def test_audit_all(tmpdir):
+    @to_task
+    def testfunc(a: int, b: float = 0.1) -> ty.NamedTuple("Output", [("out", float)]):
+        return a + b
+
     funky = testfunc(a=2, audit_flags=AuditFlag.ALL, messengers=FileMessenger())
     message_path = tmpdir / funky.checksum / "messages"
     funky.cache_dir = tmpdir

pip install -e .[dev]
parse_log_pytest
pytest
pytest pytest-cov pytest-xdist
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
cachetools==5.5.2
certifi @ file:///croot/certifi_1671487769961/work/certifi
cfgv==3.3.1
charset-normalizer==3.4.1
cloudpickle==2.2.1
codecov==2.1.13
coverage==7.2.7
distlib==0.3.9
exceptiongroup==1.2.2
execnet==2.0.2
filelock==3.12.2
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
frozendict==2.4.6
identify==2.5.24
idna==3.10
importlib-metadata==6.7.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
lxml==5.3.1
nodeenv==1.9.1
numpy==1.21.6
packaging @ file:///croot/packaging_1671697413597/work
platformdirs==4.0.0
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
pre-commit==2.21.0
psutil==7.0.0
py @ file:///opt/conda/conda-bld/py_1644396412707/work
-e git+https://github.com/nipype/pydra.git@2b0934e339e3809971a4d966ddbd755ba5c8607e#egg=pydra
PyLD==2.0.4
pytest==7.4.4
pytest-cov==4.1.0
pytest-env==1.0.1
pytest-xdist==3.5.0
PyYAML==6.0.1
requests==2.31.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
typing_extensions==4.7.1
urllib3==2.0.7
virtualenv==20.26.6
zipp @ file:///croot/zipp_1672387121353/work

name: pydra
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - cachetools==5.5.2
      - cfgv==3.3.1
      - charset-normalizer==3.4.1
      - cloudpickle==2.2.1
      - codecov==2.1.13
      - coverage==7.2.7
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - execnet==2.0.2
      - filelock==3.12.2
      - frozendict==2.4.6
      - identify==2.5.24
      - idna==3.10
      - importlib-metadata==6.7.0
      - lxml==5.3.1
      - nodeenv==1.9.1
      - numpy==1.21.6
      - platformdirs==4.0.0
      - pre-commit==2.21.0
      - psutil==7.0.0
      - pydra==0.0.1+380.g2b0934e3
      - pyld==2.0.4
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - pytest-env==1.0.1
      - pytest-xdist==3.5.0
      - pyyaml==6.0.1
      - requests==2.31.0
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - virtualenv==20.26.6
prefix: /opt/conda/envs/pydra

pydra/engine/tests/test_task.py::test_annotated_func_multreturn_exception pydra/engine/tests/test_task.py::test_halfannotated_func pydra/engine/tests/test_task.py::test_notannotated_func_multreturn
pydra/engine/tests/test_task.py::test_audit_prov
pydra/engine/tests/test_task.py::test_output pydra/engine/tests/test_task.py::test_annotated_func pydra/engine/tests/test_task.py::test_annotated_func_multreturn pydra/engine/tests/test_task.py::test_halfannotated_func_multreturn pydra/engine/tests/test_task.py::test_notannotated_func pydra/engine/tests/test_task.py::test_exception_func pydra/engine/tests/test_task.py::test_shell_cmd pydra/engine/tests/test_task.py::test_container_cmds pydra/engine/tests/test_task.py::test_docker_cmd
.travis.yml pydra/engine/task.py
.travis.yml pydra/engine/task.py