I think this should go a little further. At this point, CheckInputScripts is essentially meant to be a mempool function and PrepareInputScriptChecks is for block validation, except for the weird edge-case  when we’re single-threaded.
I think I’d rather just see this go all the way and make the split purposeful. Something like:
 0diff --git a/src/validation.cpp b/src/validation.cpp
 1index 1c81620889f..d9a447b460f 100644
 2--- a/src/validation.cpp
 3+++ b/src/validation.cpp
 4@@ -2723,19 +2723,18 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
 5         {
 6             bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
 7             TxValidationState tx_state;
 8-            // If parallel script checking is possible (worker threads are available) the checks are appended to a
 9-            // vector without running them. The vector is then added to control which runs the checks asynchronously.
10-            // Otherwise, CheckInputScripts runs the checks on a single thread before returning.
11-            if (control) {
12-                std::vector<CScriptCheck> vChecks = PrepareInputScriptChecks(tx, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);
13-                control->Add(std::move(vChecks));
14-            } else {
15-                if (!CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache)) {
16-                    // Any transaction validation failure in ConnectBlock is a block consensus failure
17-                    state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
18-                                  tx_state.GetRejectReason(), tx_state.GetDebugMessage());
19-                    break;
20+            // If parallel script checking is possible (worker threads are available), they are added to control which
21+            // which runs the checks asynchronously. Otherwise they are run here directly.
22+            std::vector<CScriptCheck> vChecks = PrepareInputScriptChecks(tx, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);
23+            if (control) control->Add(std::move(vChecks));
24+            else {
25+                for (auto& check : vChecks) {
26+                    if (const auto& check_result = check()) {
27+                        state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check_result->first)), check_result->second);
28+                        break;
29+                    }
30                 }
31+                if (!state.IsValid()) break;
32             }
33         }
An alternative would be to make CCheckQueueControl work with 0 worker threads (maybe it already does?) and just always use it. That would completely unify the approach here.