diff --git a/news/16.feature b/news/16.feature
new file mode 100644
index 0000000..56c5040
--- /dev/null
+++ b/news/16.feature
@@ -0,0 +1,1 @@
+Updated ``misc.run`` to accept new arguments for ``spinner``, ``combine_stderr``, and ``display_limit``.
diff --git a/src/vistir/misc.py b/src/vistir/misc.py
index 723bb11..44607a9 100644
--- a/src/vistir/misc.py
+++ b/src/vistir/misc.py
@@ -75,7 +75,7 @@ def dedup(iterable):
     return iter(OrderedDict.fromkeys(iterable))
 
 
-def _spawn_subprocess(script, env={}, block=True, cwd=None):
+def _spawn_subprocess(script, env={}, block=True, cwd=None, combine_stderr=True):
     from distutils.spawn import find_executable
 
     command = find_executable(script.command)
@@ -83,7 +83,7 @@ def _spawn_subprocess(script, env={}, block=True, cwd=None):
         "env": env,
         "universal_newlines": True,
         "stdout": subprocess.PIPE,
-        "stderr": subprocess.PIPE if block else subprocess.STDOUT,
+        "stderr": subprocess.PIPE if not combine_stderr else subprocess.STDOUT,
         "shell": False,
     }
     if not block:
@@ -117,58 +117,90 @@ def _create_subprocess(
     cwd=os.curdir,
     verbose=False,
     spinner=None,
+    combine_stderr=False,
+    display_limit=200
 ):
     try:
-        c = _spawn_subprocess(cmd, env=env, block=block, cwd=cwd)
+        c = _spawn_subprocess(cmd, env=env, block=block, cwd=cwd,
+                                                    combine_stderr=combine_stderr)
     except Exception as exc:
         print("Error %s while executing command %s", exc, " ".join(cmd._parts))
         raise
     if not block:
         c.stdin.close()
         output = []
+        err = []
         spinner_orig_text = ""
         if spinner:
             spinner_orig_text = spinner.text
-        if c.stdout is not None:
-            while True:
-                line = to_text(c.stdout.readline())
+        streams = {
+            "stdout": c.stdout,
+            "stderr": c.stderr
+        }
+        while True:
+            stdout_line = None
+            stderr_line = None
+            for outstream in streams.keys():
+                stream = streams[outstream]
+                if not stream:
+                    continue
+                line = to_text(stream.readline())
                 if not line:
-                    break
+                    continue
                 line = line.rstrip()
-                output.append(line)
-                display_line = line
-                if len(line) > 200:
-                    display_line = "{0}...".format(line[:200])
+                if outstream == "stderr":
+                    stderr_line = line
+                else:
+                    stdout_line = line
+            if not (stdout_line or stderr_line):
+                break
+            if stderr_line:
+                err.append(line)
+            if stdout_line:
+                output.append(stdout_line)
+                display_line = stdout_line
+                if len(stdout_line) > display_limit:
+                    display_line = "{0}...".format(stdout_line[:display_limit])
                 if verbose:
                     spinner.write(display_line)
-                else:
-                    spinner.text = "{0} {1}".format(spinner_orig_text, display_line)
-                    continue
+                spinner.text = "{0} {1}".format(spinner_orig_text, display_line)
+                continue
         try:
             c.wait()
         finally:
             if c.stdout:
                 c.stdout.close()
+            if c.stderr:
+                c.stderr.close()
         if spinner:
+            if c.returncode > 0:
+                spinner.fail("Failed...cleaning up...")
             spinner.text = "Complete!"
             spinner.ok("✔")
-        c.out = "".join(output)
-        c.err = ""
+        c.out = "\n".join(output)
+        c.err = "\n".join(err) if err else ""
     else:
         c.out, c.err = c.communicate()
     if not return_object:
-        return c.out.strip(), c.err.strip()
+        if not block:
+            c.wait()
+        out = c.out if c.out else ""
+        err = c.err if c.err else ""
+        return out.strip(), err.strip()
     return c
 
 
 def run(
     cmd,
-    env={},
+    env=None,
     return_object=False,
     block=True,
     cwd=None,
     verbose=False,
     nospin=False,
+    spinner=None,
+    combine_stderr=True,
+    display_limit=200
 ):
     """Use `subprocess.Popen` to get the output of a command and decode it.
 
@@ -179,8 +211,18 @@ def run(
     :param str cwd: Current working directory contect to use for spawning the subprocess.
     :param bool verbose: Whether to print stdout in real time when non-blocking.
     :param bool nospin: Whether to disable the cli spinner.
+    :param str spinner: The name of the spinner to use if enabled, defaults to bouncingBar
+    :param bool combine_stderr: Optionally merge stdout and stderr in the subprocess, false if nonblocking.
+    :param int dispay_limit: The max width of output lines to display when using a spinner.
     :returns: A 2-tuple of (output, error) or a :class:`subprocess.Popen` object.
+
+    .. Warning:: Merging standard out and standarad error in a nonblocking subprocess
+        can cause errors in some cases and may not be ideal. Consider disabling
+        this functionality.
     """
+
+    if not env:
+        env = os.environ.copy()
     if six.PY2:
         fs_encode = partial(to_bytes, encoding=locale_encoding)
         _env = {fs_encode(k): fs_encode(v) for k, v in os.environ.items()}
@@ -188,6 +230,8 @@ def run(
             _env[fs_encode(key)] = fs_encode(val)
     else:
         _env = {k: fs_str(v) for k, v in os.environ.items()}
+    if not spinner:
+        spinner = "bouncingBar"
     if six.PY2:
         if isinstance(cmd, six.string_types):
             cmd = cmd.encode("utf-8")
@@ -195,22 +239,35 @@ def run(
             cmd = [c.encode("utf-8") for c in cmd]
     if not isinstance(cmd, Script):
         cmd = Script.parse(cmd)
+    if block or not return_object:
+        combine_stderr = False
+    sigmap = {}
     if nospin is False:
         try:
+            import signal
             from yaspin import yaspin
             from yaspin import spinners
+            from yaspin.signal_handlers import fancy_handler
         except ImportError:
             raise RuntimeError(
                 "Failed to import spinner! Reinstall vistir with command:"
                 " pip install --upgrade vistir[spinner]"
             )
         else:
-            spinner = yaspin
-            animation = spinners.Spinners.bouncingBar
+            animation = getattr(spinners.Spinners, spinner)
+            sigmap = {
+                signal.SIGINT: fancy_handler
+            }
+            if os.name == "nt":
+                sigmap.update({
+                    signal.CTRL_C_EVENT: fancy_handler,
+                    signal.CTRL_BREAK_EVENT: fancy_handler
+                })
+            spinner_func = yaspin
     else:
 
         @contextmanager
-        def spinner(spin_type, text):
+        def spinner_func(spin_type, text, **kwargs):
             class FakeClass(object):
                 def __init__(self, text=""):
                     self.text = text
@@ -225,7 +282,7 @@ def run(
             yield myobj
 
         animation = None
-    with spinner(animation, text="Running...") as sp:
+    with spinner_func(animation, sigmap=sigmap, text="Running...") as sp:
         return _create_subprocess(
             cmd,
             env=_env,
@@ -234,6 +291,7 @@ def run(
             cwd=cwd,
             verbose=verbose,
             spinner=sp,
+            combine_stderr=combine_stderr
         )
 
 
@@ -249,7 +307,8 @@ def load_path(python):
     """
 
     python = Path(python).as_posix()
-    out, err = run([python, "-c", "import json, sys; print(json.dumps(sys.path))"])
+    out, err = run([python, "-c", "import json, sys; print(json.dumps(sys.path))"],
+                        nospin=True)
     if out:
         return json.loads(out)
     else:
diff --git a/tox.ini b/tox.ini
index 99dcca3..68550f3 100644
--- a/tox.ini
+++ b/tox.ini
@@ -7,7 +7,7 @@ passenv = CI GIT_SSL_CAINFO
 setenv =
     LC_ALL = en_US.UTF-8
 deps =
-	-e .[tests]
+	-e .[spinner,tests]
 	coverage
 commands = coverage run --parallel -m pytest --timeout 300 []
 install_command = python -m pip install {opts} {packages}

Allow specifying spinner name, line length, and whether to combine stdout and stderr in "run" params
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 110bf68..f05ae4d 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -57,24 +57,34 @@ def test_dedup():
 
 
 def test_run():
-    out, err = vistir.misc.run(["python", "-c", "print('hello')"])
+    out, err = vistir.misc.run(["python", "-c", "print('hello')"], nospin=True)
     assert out == "hello"
-    out, err = vistir.misc.run(["python", "-c", "import ajwfoiejaoiwj"])
-    assert any(error_text in err for error_text in ["ImportError", "ModuleNotFoundError"])
+    out, err = vistir.misc.run(["python", "-c", "import ajwfoiejaoiwj"], nospin=True)
+    assert any(error_text in err for error_text in ["ImportError", "ModuleNotFoundError"]), "{0} => {1}".format(out, err)
 
 
 def test_run_return_subprocess():
-    c = vistir.misc.run(["python", "-c", "print('test')"], return_object=True)
+    c = vistir.misc.run(["python", "-c", "print('test')"], return_object=True, nospin=True)
     assert c.returncode == 0
     assert c.out.strip() == "test"
 
 
 def test_nonblocking_run():
-    c = vistir.misc.run(["python", "--help"], block=False, return_object=True)
+    c = vistir.misc.run(["python", "--help"], block=False, return_object=True, nospin=True)
     assert c.returncode == 0
-    assert "PYTHONHOME" in c.out
-    out, err = vistir.misc.run(["python", "--help"], block=False)
-    assert "PYTHONHOME" in out
+    c.wait()
+    assert "PYTHONDONTWRITEBYTECODE" in c.out, c.out
+    out, err = vistir.misc.run(["python", "--help"], block=False, nospin=True)
+    assert "PYTHONDONTWRITEBYTECODE" in out, out
+    # historical = []
+    # while out:
+    #     pos = out.find("\n")
+    #     if not pos:
+    #         historical.append(out)
+    #     line, _, out = out.partition("\n")
+    #     if line not in historical:
+    #         historical.append(line)
+    # assert any(["PYTHONHOME" in line for line in historical]), historical
 
 
 def test_load_path():

pip install -e .[tests]
parse_log_pytest
requirements.txt
pytest
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
charset-normalizer==2.0.12
coverage==6.2
execnet==1.9.0
hypothesis==6.31.6
hypothesis-fspaths==0.1
idna==3.10
importlib-metadata==4.8.3
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
pytest-timeout==2.1.0
pytest-xdist==3.0.2
requests==2.27.1
six==1.17.0
sortedcontainers==2.4.0
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
-e git+https://github.com/sarugaku/vistir.git@99262588e04434528f1c4bfa3b3c9e6bb7cabf0b#egg=vistir
zipp==3.6.0

name: vistir
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
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - 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
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - charset-normalizer==2.0.12
      - coverage==6.2
      - execnet==1.9.0
      - hypothesis==6.31.6
      - hypothesis-fspaths==0.1
      - idna==3.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - pytest-timeout==2.1.0
      - pytest-xdist==3.0.2
      - requests==2.27.1
      - six==1.17.0
      - sortedcontainers==2.4.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/vistir

tests/test_misc.py::test_load_path
tests/test_misc.py::test_shell_escape tests/test_misc.py::test_unnest tests/test_misc.py::test_dedup tests/test_misc.py::test_run tests/test_misc.py::test_run_return_subprocess tests/test_misc.py::test_nonblocking_run tests/test_misc.py::test_partialclass
tox.ini news/16.feature src/vistir/misc.py
tox.ini news/16.feature src/vistir/misc.py