diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,24 @@
 # Changelog
 
+## [0.33.2.0] - 2026-10-24
+
+### Fixed
+
+* The warning to compile with `-threaded -rtsopts -with-rtsopts=-N` also fires
+  when the thread count was asked for, such as `--jobs 4` on one capability.
+
+## [0.33.1.0] - 2026-10-24
+
+### Added
+
+* `NIX_BUILD_CORES` narrows the thread count to the cores nix gave the build,
+  when nothing else says how parallel to run.
+
+### Fixed
+
+* Zero threads means every capability, rather than an asynchronous runner with
+  no workers to take tests off the queue.
+
 ## [0.33.0.0] - 2026-10-01
 
 ### Fixed
diff --git a/src/Test/Syd/MutationMode/Common.hs b/src/Test/Syd/MutationMode/Common.hs
--- a/src/Test/Syd/MutationMode/Common.hs
+++ b/src/Test/Syd/MutationMode/Common.hs
@@ -459,14 +459,14 @@
       ]
 
 -- | Render the per-mutation progress line emitted as each mutation is
--- tested.  @index@\/@total@ are this mutation's 1-based position in the run
--- and the total mutation count, shown as a coloured @[X\/Y]@ counter.  The
--- @verbose@ flag controls detail: in concise mode (the default) this is the
--- single locating line @[X\/Y] Testing mutation \<operator\> at
--- \<file\>:\<line\>:\<cols\>@, so a long run shows steady progress — including
--- how far along it is — without flooding the log; in verbose\/debug mode the
--- full source diff of the mutation follows — the same block the report prints
--- for a survivor.
+-- tested.  @index@\/@total@ are how many mutations have started, this one
+-- included, and how many the run tests in total, shown as a coloured
+-- @[X\/Y]@ counter.  The @verbose@ flag controls detail: in concise mode
+-- (the default) this is the single locating line @[X\/Y] Testing mutation
+-- \<operator\> at \<file\>:\<line\>:\<cols\>@, so a long run shows steady
+-- progress — including how far along it is — without flooding the log; in
+-- verbose\/debug mode the full source diff of the mutation follows — the
+-- same block the report prints for a survivor.
 renderMutationProgressEvent :: Bool -> Int -> Int -> MutationProgressEvent -> [[Chunk]]
 renderMutationProgressEvent verbose index total (MutationProgressEvent rec) =
   let logLines = formatMutationLog (augmentedMutationRecordId rec) rec
diff --git a/src/Test/Syd/OptParse.hs b/src/Test/Syd/OptParse.hs
--- a/src/Test/Syd/OptParse.hs
+++ b/src/Test/Syd/OptParse.hs
@@ -12,7 +12,6 @@
 import Autodocodec
 import Control.Applicative
 import Control.Concurrent (getNumCapabilities)
-import Control.Monad
 import Control.Monad.IO.Class
 import Data.List.NonEmpty (NonEmpty (..))
 import Data.Maybe
@@ -170,35 +169,34 @@
             Just True -> pure WithoutColours
             _ -> detectTerminalCapabilities
 
-        let threads =
+        let configuredThreads =
               fromMaybe
                 ( if flagDebug
                     then Synchronous
                     else d settingThreads
                 )
                 flagThreads
-        case threads of
-          ByCapabilities -> do
-            i <- getNumCapabilities
-
-            when (i == 1) $ do
-              let outputLine :: [Chunk] -> IO ()
-                  outputLine lineChunks =
-                    liftIO $
-                      -- Emit UTF-8 bytes directly so output never depends on the
-                      -- handle's locale encoding; the trailing newline is folded
-                      -- into the chunks to keep this one byte-level write.
-                      putChunksUtf8With terminalCapabilities (lineChunks <> [chunk "\n"])
-              mapM_
-                ( outputLine
-                    . (: [])
-                    . fore red
-                )
-                [ chunk "WARNING: Only one CPU core detected, make sure to compile your test suite with these ghc options:",
-                  chunk "         -threaded -rtsopts -with-rtsopts=-N",
-                  chunk "         (This is important for correctness as well as speed, as a parallel test suite can find thread safety problems.)"
-                ]
-          _ -> pure ()
+        numCapabilities <- getNumCapabilities
+        let threads = threadsWithNixBuildCores numCapabilities flagNixBuildCores configuredThreads
+        case oneCapabilityWarning numCapabilities threads of
+          WarnAboutOneCapability -> do
+            let outputLine :: [Chunk] -> IO ()
+                outputLine lineChunks =
+                  liftIO $
+                    -- Emit UTF-8 bytes directly so output never depends on the
+                    -- handle's locale encoding; the trailing newline is folded
+                    -- into the chunks to keep this one byte-level write.
+                    putChunksUtf8With terminalCapabilities (lineChunks <> [chunk "\n"])
+            mapM_
+              ( outputLine
+                  . (: [])
+                  . fore red
+              )
+              [ chunk "WARNING: This test suite can only use one capability, make sure to compile it with these ghc options:",
+                chunk "         -threaded -rtsopts -with-rtsopts=-N",
+                chunk "         (This is important for correctness as well as speed, as a parallel test suite can find thread safety problems.)"
+              ]
+          NoOneCapabilityWarning -> pure ()
         errOrProgress <- case flagReportProgress of
           Nothing ->
             pure $
@@ -377,6 +375,7 @@
   { flagSeed :: !SeedSetting,
     flagRandomiseExecutionOrder :: !(Maybe Bool),
     flagThreads :: !(Maybe Threads),
+    flagNixBuildCores :: !(Maybe Word),
     flagMaxSize :: !Int,
     flagMaxSuccess :: !Int,
     flagMaxDiscard :: !Int,
@@ -420,6 +419,7 @@
             name "randomize-execution-order"
           ]
     flagThreads <- optional settingsParser
+    flagNixBuildCores <- nixBuildCoresParser
     flagMaxSize <-
       setting
         [ help "Maximum size parameter to pass to generators",
@@ -702,13 +702,53 @@
     Asynchronous !Word
   deriving (Show, Read, Eq, Generic)
 
+-- | Whether to tell a test suite's author that it can only use one
+-- capability.
+data OneCapabilityWarning
+  = WarnAboutOneCapability
+  | NoOneCapabilityWarning
+  deriving (Show, Eq)
+
+-- | Whether a test suite that would run tests at the same time can do so.
+--
+-- A suite with one capability runs its tests one at a time, so it gets
+-- neither the speed of running them at the same time nor the thread safety
+-- problems that only tests running at the same time find.  Compiling with
+-- @-threaded@ and @-N@ is what raises the capability count, which is why
+-- that is the advice, and it is advice about the suite rather than about the
+-- machine it is on: a suite built without @-N@ has one capability on every
+-- machine, so a single-processor machine is worth warning on too, even
+-- though nothing would run at the same time there anyway.
+--
+-- Only a suite that would use more capabilities has anything to gain.
+-- 'Synchronous' would not, and neither would an 'Asynchronous' bound of a
+-- single test.
+oneCapabilityWarning :: Int -> Threads -> OneCapabilityWarning
+oneCapabilityWarning numCapabilities threads =
+  if numCapabilities <= 1
+    then case threads of
+      Synchronous -> NoOneCapabilityWarning
+      ByCapabilities -> WarnAboutOneCapability
+      Asynchronous bound ->
+        if bound > 1
+          then WarnAboutOneCapability
+          else NoOneCapabilityWarning
+    else NoOneCapabilityWarning
+
+-- | How parallel to run the test suite, from a number of threads.
+--
+-- 0 means every capability, both because zero threads would run no tests at
+-- all, and because that is what nix means by 0 cores.
+threadsFromWord :: Word -> Threads
+threadsFromWord = \case
+  0 -> ByCapabilities
+  1 -> Synchronous
+  w -> Asynchronous w
+
 instance HasCodec Threads where
   codec = dimapCodec f g codec
     where
-      f = \case
-        Nothing -> ByCapabilities
-        Just 1 -> Synchronous
-        Just n -> Asynchronous n
+      f = maybe ByCapabilities threadsFromWord
       g = \case
         ByCapabilities -> Nothing
         Synchronous -> Just 1
@@ -717,12 +757,9 @@
 instance HasParser Threads where
   settingsParser =
     choice
-      [ ( \case
-            1 -> Synchronous
-            w -> Asynchronous w
-        )
+      [ threadsFromWord
           <$> setting
-            [ help "How many threads to use to execute tests in asynchronously",
+            [ help "How many threads to use to execute tests in asynchronously, 0 means as many as you have capabilities",
               reader auto,
               option,
               long "jobs",
@@ -745,14 +782,43 @@
             ],
         setting
           [ help "How parallel to run the test suite",
-            confWith' "threads" $
-              let f = \case
-                    Nothing -> Just ByCapabilities
-                    Just 1 -> Just Synchronous
-                    Just n -> Just $ Asynchronous n
-               in f <$> codec
+            confWith' "threads" $ Just <$> codec
           ]
       ]
+
+-- | Nix tells a build how many cores it may use with its @cores@ setting,
+-- which reaches the build only as the @NIX_BUILD_CORES@ environment
+-- variable.
+nixBuildCoresParser :: Parser (Maybe Word)
+nixBuildCoresParser =
+  optional $
+    setting
+      [ help "How many threads to use to execute tests in asynchronously, if nothing else says",
+        reader auto,
+        unprefixedEnv "NIX_BUILD_CORES",
+        metavar "INT"
+      ]
+
+-- | Narrow "as many threads as you have capabilities" down to the cores nix
+-- gave the build.
+--
+-- Only that case is narrowed, so that anything configured explicitly still
+-- decides.
+--
+-- Narrowed, never widened: more cores than the RTS has capabilities would
+-- run more tests at once than it can overlap, which is contention without
+-- any parallelism to show for it.  A suite not compiled with @-N@ has a
+-- single capability, and running it 64 ways because the builder has 64 cores
+-- would be a change for the worse.
+threadsWithNixBuildCores :: Int -> Maybe Word -> Threads -> Threads
+threadsWithNixBuildCores numCapabilities mCores = \case
+  ByCapabilities -> case mCores of
+    Nothing -> ByCapabilities
+    Just cores
+      | cores >= fromIntegral numCapabilities -> ByCapabilities
+      | otherwise -> threadsFromWord cores
+  Synchronous -> Synchronous
+  Asynchronous w -> Asynchronous w
 
 data Iterations
   = -- | Run the test suite once, the default
diff --git a/sydtest.cabal b/sydtest.cabal
--- a/sydtest.cabal
+++ b/sydtest.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           sydtest
-version:        0.33.0.0
+version:        0.33.2.0
 synopsis:       A modern testing framework for Haskell with good defaults and advanced testing features.
 description:    A modern testing framework for Haskell with good defaults and advanced testing features. Sydtest aims to make the common easy and the hard possible. See https://github.com/NorfairKing/sydtest#readme for more information.
 category:       Testing
