Skip redundant second replay for single-input non-persistent crash batches.

During crash replay (`ReplayCrash`), Centipede executes a batch containing a single crashing input (`input_vec.size() == 1`). Previously, when that input crashed, `Centipede::ReportCrash` re-executed the binary a second time to isolate the reproducer from the batch, and skipped writing crash metadata (`<hash>.desc` and `<hash>.sig`) if the second execution timed out (for example, due to slow stack trace printing in the signal handler), failed to reproduce a flaky crash, or produced a different signature. When crash metadata files were not written, `GetCrashesFromWorkdir` returned zero crashes (`Total crashes: 0`) and `CrashSummary` omitted the reproduced crash.

Disable `persistent_mode` in `ReplayCrash` so the single crashing input runs in an isolated process, and update `Centipede::ExecuteAndReportCrash` and `Centipede::ReportCrash` to save the reproducer and crash metadata directly from `batch_result` without re-executing the binary when `input_vec.size() == 1` and `persistent_mode` is disabled.

PiperOrigin-RevId: 986722767
diff --git a/centipede/centipede.cc b/centipede/centipede.cc
index c81217d..7566883 100644
--- a/centipede/centipede.cc
+++ b/centipede/centipede.cc
@@ -111,6 +111,37 @@
   return mutants;
 }
 
+void SaveReproducer(const WorkDir& wd, std::string_view log_prefix,
+                    size_t input_idx, ByteSpan reproducer,
+                    const BatchResult& result) {
+  auto hash = Hash(reproducer);
+  auto crash_dir = wd.CrashReproducerDirPaths().MyShard();
+  FUZZTEST_CHECK_OK(RemoteMkdir(crash_dir));
+  std::string input_file_path = std::filesystem::path(crash_dir) / hash;
+  auto crash_metadata_dir = wd.CrashMetadataDirPaths().MyShard();
+  FUZZTEST_CHECK_OK(RemoteMkdir(crash_metadata_dir));
+  std::string crash_metadata_path_prefix =
+      std::filesystem::path(crash_metadata_dir) / hash;
+  FUZZTEST_LOG(INFO)
+      << log_prefix << "Detected crash-reproducing input:"
+      << "\nInput index    : " << input_idx
+      << "\nInput bytes    : " << AsPrintableString(reproducer, /*max_len=*/32)
+      << "\nExit code      : " << result.exit_code()
+      << "\nFailure        : " << result.failure_description()
+      << "\nSignature      : "
+      << AsPrintableString(AsByteSpan(result.failure_signature()),
+                           /*max_len=*/32)
+      << "\nSaving input to: " << input_file_path << "\nSaving crash"  //
+      << "\nmetadata to    : " << crash_metadata_path_prefix << ".*";
+  FUZZTEST_CHECK_OK(RemoteFileSetContents(input_file_path, reproducer));
+  FUZZTEST_CHECK_OK(
+      RemoteFileSetContents(absl::StrCat(crash_metadata_path_prefix, ".desc"),
+                            result.failure_description()));
+  FUZZTEST_CHECK_OK(
+      RemoteFileSetContents(absl::StrCat(crash_metadata_path_prefix, ".sig"),
+                            result.failure_signature()));
+}
+
 }  // namespace
 
 Centipede::Centipede(const Environment& env, CentipedeCallbacks& user_callbacks,
@@ -396,7 +427,11 @@
                        << batch_result.failure_description();
     return true;
   }
-  if (stop_condition_.ShouldStop()) {
+  const bool can_report_without_reexecution =
+      input_vec.size() == 1 && !env_.persistent_mode &&
+      batch_result.IsInputFailure() && !stop_condition_.StopRequested() &&
+      batch_result.failure_description() != kExecutionFailurePerBatchTimeout;
+  if (stop_condition_.ShouldStop() && !can_report_without_reexecution) {
     FUZZTEST_LOG_FIRST_N(WARNING, 1)
         << "Crash found but the stop condition is met - not reporting further "
            "possibly related crashes.";
@@ -1059,6 +1094,17 @@
     return;
   }
 
+  if (input_vec.size() == 1 && !env_.persistent_mode &&
+      batch_result.IsInputFailure()) {
+    FUZZTEST_LOG(INFO)
+        << log_prefix
+        << "Single-input batch in non-persistent mode; skipping re-execution "
+           "and saving the reproducer directly.";
+    SaveReproducer(wd_, log_prefix, /*input_idx=*/0, input_vec[0],
+                   batch_result);
+    return;
+  }
+
   // Determine the optimal order of the inputs to try to maximize the chances of
   // finding the reproducer fast.
   std::vector<size_t> input_idxs_to_try;
@@ -1095,34 +1141,8 @@
           one_input_batch_result.failure_signature() ==
               batch_result.failure_signature() &&
           !stop_condition_.ShouldStop()) {
-        auto hash = Hash(one_input);
-        auto crash_dir = wd_.CrashReproducerDirPaths().MyShard();
-        FUZZTEST_CHECK_OK(RemoteMkdir(crash_dir));
-        std::string input_file_path = std::filesystem::path(crash_dir) / hash;
-        auto crash_metadata_dir = wd_.CrashMetadataDirPaths().MyShard();
-        FUZZTEST_CHECK_OK(RemoteMkdir(crash_metadata_dir));
-        std::string crash_metadata_path_prefix =
-            std::filesystem::path(crash_metadata_dir) / hash;
-        FUZZTEST_LOG(INFO)
-            << log_prefix << "Detected crash-reproducing input:"
-            << "\nInput index    : " << input_idx << "\nInput bytes    : "
-            << AsPrintableString(one_input, /*max_len=*/32)
-            << "\nExit code      : " << one_input_batch_result.exit_code()
-            << "\nFailure        : "
-            << one_input_batch_result.failure_description()
-            << "\nSignature      : "
-            << AsPrintableString(
-                   AsByteSpan(one_input_batch_result.failure_signature()),
-                   /*max_len=*/32)
-            << "\nSaving input to: " << input_file_path << "\nSaving crash"  //
-            << "\nmetadata to    : " << crash_metadata_path_prefix << ".*";
-        FUZZTEST_CHECK_OK(RemoteFileSetContents(input_file_path, one_input));
-        FUZZTEST_CHECK_OK(RemoteFileSetContents(
-            absl::StrCat(crash_metadata_path_prefix, ".desc"),
-            one_input_batch_result.failure_description()));
-        FUZZTEST_CHECK_OK(RemoteFileSetContents(
-            absl::StrCat(crash_metadata_path_prefix, ".sig"),
-            one_input_batch_result.failure_signature()));
+        SaveReproducer(wd_, log_prefix, input_idx, one_input,
+                       one_input_batch_result);
         return;
       }
     }
diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc
index 44ba0c9..e6ba1eb 100644
--- a/centipede/centipede_interface.cc
+++ b/centipede/centipede_interface.cc
@@ -670,6 +670,7 @@
       crash_corpus_config, env.binary_name, env.binary_hash));
   Environment run_crash_env = env;
   run_crash_env.load_shards_only = true;
+  run_crash_env.persistent_mode = false;
   Fuzz(run_crash_env, {}, "", callbacks_factory, stop_condition);
   if (env.report_crash_summary) {
     CrashSummary crash_summary{env.fuzztest_binary_identifier, env.test_name};
diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc
index 1eae7ea..ac1ade9 100644
--- a/centipede/centipede_test.cc
+++ b/centipede/centipede_test.cc
@@ -1046,6 +1046,39 @@
   EXPECT_TRUE(std::filesystem::exists(crasher_path)) << crasher_path;
 }
 
+TEST(Centipede, SingleInputNonPersistentSkipsTriageReplay) {
+  TempDir temp_dir{test_info_->name()};
+  Environment env;
+  env.workdir = temp_dir.path();
+  env.num_runs = 5;
+  env.batch_size = 1;
+  env.persistent_mode = false;
+  env.require_pc_table = false;
+  env.exit_on_crash = true;
+
+  // Fail on first pass for input 2, and never fail in triage
+  // (`fail_on_triage_attempt = 99`).
+  FlakyCrashingInputMock mock(env, /*crashing_input_idx=*/2,
+                              /*fail_on_triage_attempt=*/99);
+  NonOwningCallbacksFactory factory(mock);
+  CentipedeMain(env, factory);
+
+  // Triage re-execution must be skipped (`triage_attempts() == 0`) and the
+  // reproducer + metadata files must still be written.
+  EXPECT_EQ(mock.triage_attempts(), 0);
+  const auto crashing_input_hash = Hash(mock.crashing_input());
+  const auto crasher_path =
+      std::filesystem::path{WorkDir{env}.CrashReproducerDirPaths().MyShard()} /
+      crashing_input_hash;
+  EXPECT_TRUE(std::filesystem::exists(crasher_path)) << crasher_path;
+  const auto metadata_dir =
+      std::filesystem::path{WorkDir{env}.CrashMetadataDirPaths().MyShard()};
+  EXPECT_TRUE(
+      std::filesystem::exists(metadata_dir / (crashing_input_hash + ".desc")));
+  EXPECT_TRUE(
+      std::filesystem::exists(metadata_dir / (crashing_input_hash + ".sig")));
+}
+
 TEST_F(CentipedeWithTemporaryLocalDir, GetsSeedInputs) {
   Environment env;
   env.binary =