diff --git a/dnadna/utils/cli.py b/dnadna/utils/cli.py
index 5cc2e7a861e81a3f07256eed1ddf0dd5e300685a..51da4579da40a16dd4782915ace96b3ee71018cb 100644
--- a/dnadna/utils/cli.py
+++ b/dnadna/utils/cli.py
@@ -9,6 +9,7 @@ import os.path as pth
 import shutil
 import sys
 import textwrap
+import traceback
 
 try:
     import coloredlogs
@@ -19,6 +20,9 @@ from .misc import unique, stdio_redirect_tqdm
 from .plugins import load_plugins
 
 
+log = logging.getLogger(__name__)
+
+
 # Log filter for filtering out uninteresting messages from other libraries
 class DNADNAFilter(logging.Filter):
     def __init__(self, my_level):
@@ -167,11 +171,15 @@ class Command(metaclass=abc.ABCMeta):
         # a command that does not normally configure logging
         log_file = getattr(args, 'log_file', None)
 
+        # Delete any existing handlers on the root logger, so if this is called
+        # multiple times it will reset the root logger configuration.
+        del logging.getLogger().handlers[:]
+
         # stdio_redirect_tqdm is required so that stdout logging will cooperate
         # with any commands that use a tqdm progress bar; if they don't use
         # tqdm this will just write through to stdout as normal
         with stdio_redirect_tqdm():
-            if coloredlogs:
+            if coloredlogs and not getattr(args, 'log_nc', False):
                 # Annoying inconsistency between coloredlogs.install and
                 # logging.basicConfig
                 logging_config = dict(cls.logging_config)
@@ -218,7 +226,7 @@ class Command(metaclass=abc.ABCMeta):
                 f'unknown subcommand "{command[0]}"; known subcommands '
                 f'are:\n {subcommands}')
 
-        return command_cls.main(command[1:], namespace=args, is_subcommand=True)
+        return command_cls.main(command[1:], namespace=args)
 
     @classmethod
     def exit_error(cls, msg='', exit_code=1):
@@ -229,7 +237,7 @@ class Command(metaclass=abc.ABCMeta):
         sys.exit(exit_code)
 
     @classmethod
-    def main(cls, argv=None, namespace=None, is_subcommand=False):
+    def main(cls, argv=None, namespace=None):
         args, argv = cls.preparse_args(argv, namespace=namespace)
 
         # Special handling of program name if the __main__ module is run:
@@ -256,16 +264,33 @@ class Command(metaclass=abc.ABCMeta):
                     ret = ret2
             return ret
         except Exception as exc:
-            if not is_subcommand:
-                print(f'an unexpected error occurred: {exc}; run again with '
-                      f'--debug to view the full traceback or with --pdb to '
-                      f'drop into a debugger', file=sys.stderr)
+            msgs = [f'{type(exc).__name__}: {exc}']
+
+            if not args.debug:
+                msgs.append(
+                    'run again with --debug to view the full traceback, '
+                    'or with --pdb to drop into a debugger')
+
+            if cls.logging:
+                if args.debug:
+                    log_method = log.exception
+                else:
+                    log_method = log.error
+
+                for msg in msgs:
+                    log_method(msg)
+            else:
+                msgs.insert(0, 'error:')
+                for msg in msgs:
+                    print(msg, '\n', file=sys.stderr)
+
+                if args.debug:
+                    print(file=sys.stderr)
+                    traceback.print_exc()
 
             if args.pdb:
                 import pdb
                 pdb.post_mortem()
-            elif args.debug or is_subcommand:
-                raise exc
 
             errno = getattr(exc, 'errno', 1)
             return errno
diff --git a/tests/test_cli.rst b/tests/test_cli.rst
index 9f97bcdf9e249767aeed4ac7b1a553150766a3fb..5d9cef079f3e659f5df073a5f9bb7e77d1ddc0a9 100644
--- a/tests/test_cli.rst
+++ b/tests/test_cli.rst
@@ -172,8 +172,18 @@ Now same test but with the ``one_event`` simulator, which does require the
     ...
     >>> monkeypatch.setattr(SimulationCommand, 'run', raise_exception)
     >>> run_main(['dnadna', 'simulation'])
-    an unexpected error occurred: test exception; run again with --debug to view the full traceback or with --pdb to drop into a debugger
+    error:
+    <BLANKLINE>
+    RuntimeError: test exception
+    <BLANKLINE>
+    run again with --debug to view the full traceback, or with --pdb to drop
+    into a debugger
     >>> run_main(['dnadna', 'simulation', '--debug'])
+    error:
+    <BLANKLINE>
+    RuntimeError: test exception
+    <BLANKLINE>
+    <BLANKLINE>
     Traceback (most recent call last):
     ...
     RuntimeError: test exception
@@ -184,8 +194,18 @@ the code)::
 
     >>> monkeypatch.setattr(SimulationInitCommand, 'run', raise_exception)
     >>> run_main(['dnadna', 'simulation', 'init'])
-    an unexpected error occurred: test exception; run again with --debug to view the full traceback or with --pdb to drop into a debugger
+    error:
+    <BLANKLINE>
+    RuntimeError: test exception
+    <BLANKLINE>
+    run again with --debug to view the full traceback, or with --pdb to drop
+    into a debugger
     >>> run_main(['dnadna', 'simulation', 'init', '--debug'])
+    error:
+    <BLANKLINE>
+    RuntimeError: test exception
+    <BLANKLINE>
+    <BLANKLINE>
     Traceback (most recent call last):
     ...
     RuntimeError: test exception