Minimize new crash inputs updating corpus database.

This is enabled and and capped by the new flag `minimize_corpus_database_new_crashes_duration`.

PiperOrigin-RevId: 828029112
diff --git a/centipede/BUILD b/centipede/BUILD
index 5e1690e..0c63299 100644
--- a/centipede/BUILD
+++ b/centipede/BUILD
@@ -384,6 +384,7 @@
         "@abseil-cpp//absl/base:core_headers",
         "@abseil-cpp//absl/container:flat_hash_set",
         "@abseil-cpp//absl/synchronization",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
         "@com_google_fuzztest//common:logging",
     ],
@@ -904,6 +905,7 @@
         ":centipede_callbacks",
         ":crash_summary",
         ":environment",
+        ":minimize_crash",
         ":runner_result",
         ":stop",
         ":workdir",
diff --git a/centipede/centipede_flags.inc b/centipede/centipede_flags.inc
index 2a21f1e..d459b16 100644
--- a/centipede/centipede_flags.inc
+++ b/centipede/centipede_flags.inc
@@ -532,3 +532,7 @@
     "or deduplication before considering it non-reproducible.")
 CENTIPEDE_FLAG(int, replay_coverage_attempts, 1,
                "Number of attempts to replay coverage/corpus inputs.")
+CENTIPEDE_FLAG(absl::Duration, fuzztest_minimize_new_crashes_duration,
+               absl::ZeroDuration(),
+               "The time limit for minimizing new crashes when updating the "
+               "FuzzTest corpus database.")
diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc
index fea652e..ee22718 100644
--- a/centipede/centipede_interface.cc
+++ b/centipede/centipede_interface.cc
@@ -123,7 +123,9 @@
     ScopedCentipedeCallbacks scoped_callbacks(callbacks_factory, env,
                                               stop_condition);
     CreateLocalDirRemovedAtExit(TemporaryLocalDirPath());
-    if (scoped_callbacks.callbacks()->Execute(env.binary, {crashy_input},
+    const std::vector<ByteSpan> inputs(
+        std::max<size_t>(1, env.replay_crash_attempts), crashy_input);
+    if (scoped_callbacks.callbacks()->Execute(env.binary, inputs,
                                               batch_result)) {
       FUZZTEST_LOG(ERROR) << "The original crashy input did not crash; exiting";
       stop_condition.RequestStop(EXIT_FAILURE,
diff --git a/centipede/crash_deduplication.cc b/centipede/crash_deduplication.cc
index dbc44ba..0e11b2d 100644
--- a/centipede/crash_deduplication.cc
+++ b/centipede/crash_deduplication.cc
@@ -38,6 +38,7 @@
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/crash_summary.h"
 #include "./centipede/environment.h"
+#include "./centipede/minimize_crash.h"
 #include "./centipede/runner_result.h"
 #include "./centipede/stop.h"
 #include "./centipede/workdir.h"
@@ -308,17 +309,14 @@
 }
 
 absl::flat_hash_map<std::string, CrashDetails> FindNewCrashes(
+    const Environment& env, CentipedeCallbacksFactory& callbacks_factory,
     const absl::flat_hash_map<std::string, CrashDetails>&
         new_crashes_by_signature,
     absl::Span<const IncubatingCrash> incubating_crashes,
-    absl::Span<const ExistingCrash> existing_crashes) {
+    absl::Span<const ExistingCrash> existing_crashes,
+    StopCondition& stop_condition) {
   absl::flat_hash_map<std::string, CrashDetails> new_crashes;
 
-  // Add new crashes from the current run.
-  for (const auto& [signature, details] : new_crashes_by_signature) {
-    new_crashes[signature] = details;
-  }
-
   // Add reproducing incubating crashes.
   for (const auto& incubating : incubating_crashes) {
     if (!incubating.new_signature.empty()) {
@@ -333,6 +331,63 @@
     }
   }
 
+  std::vector<std::string> crash_signatures_to_minimize;
+  // Add new crashes from the current run.
+  for (const auto& [signature, details] : new_crashes_by_signature) {
+    auto [it, inserted] = new_crashes.try_emplace(signature, details);
+    if (!inserted) continue;
+    crash_signatures_to_minimize.push_back(signature);
+  }
+
+  if (env.fuzztest_minimize_new_crashes_duration <= absl::ZeroDuration()) {
+    // Minimization is disabled.
+    return new_crashes;
+  }
+
+  const absl::Time previous_stop_time = stop_condition.GetStopTime();
+  const absl::Time minimization_deadline =
+      std::min(previous_stop_time,
+               absl::Now() + env.fuzztest_minimize_new_crashes_duration);
+  for (size_t i = 0; i < crash_signatures_to_minimize.size(); ++i) {
+    if (stop_condition.ShouldStop()) {
+      break;
+    }
+    const auto& signature = crash_signatures_to_minimize[i];
+    auto& details = new_crashes[signature];
+    ByteArray input_bytes;
+    if (const auto status =
+            RemoteFileGetContents(details.input_path, input_bytes);
+        !status.ok()) {
+      FUZZTEST_LOG(ERROR)
+          << "Failed to read new crash to minimize - skipping minimization";
+      continue;
+    }
+    const auto now = absl::Now();
+    // Let the remaining new crashes evenly split what's remaining for
+    // `minimization_deadline`.
+    stop_condition.SetStopTime(now +
+                               (minimization_deadline - now) /
+                                   (crash_signatures_to_minimize.size() - i));
+    const auto result = MinimizeCrash(input_bytes, env, callbacks_factory,
+                                      signature, stop_condition);
+    stop_condition.SetStopTime(previous_stop_time);
+    if (!result.has_value()) {
+      FUZZTEST_LOG(ERROR) << "Failed to find a smaller crash.";
+      continue;
+    }
+    const auto minimized_input_path = absl::StrCat(details.input_path, ".min");
+    if (const auto status =
+            RemoteFileSetContents(minimized_input_path, result->input);
+        !status.ok()) {
+      FUZZTEST_LOG(ERROR)
+          << "Failed to save the minimized crash - skipping minimization";
+      continue;
+    }
+    details.input_signature = Hash(result->input);
+    details.description = std::move(result->description);
+    details.input_path = minimized_input_path;
+  }
+
   return new_crashes;
 }
 
@@ -742,8 +797,9 @@
                                  stop_condition,
                                  absl::MakeSpan(incubating_to_replay)));
 
-  absl::flat_hash_map<std::string, CrashDetails> new_crashes = FindNewCrashes(
-      new_crashes_by_signature, incubating_crashes, existing_crashes);
+  absl::flat_hash_map<std::string, CrashDetails> new_crashes =
+      FindNewCrashes(env, callbacks_factory, new_crashes_by_signature,
+                     incubating_crashes, existing_crashes, stop_condition);
 
   std::vector<ExistingCrashAction> actions =
       ComputeExistingCrashActions(existing_crashes, new_crashes);
diff --git a/centipede/environment.cc b/centipede/environment.cc
index f17f751..bef0b48 100644
--- a/centipede/environment.cc
+++ b/centipede/environment.cc
@@ -253,6 +253,8 @@
   if (replay_crash_attempts == Default().replay_crash_attempts) {
     replay_crash_attempts = config.replay_crash_attempts;
   }
+  fuzztest_minimize_new_crashes_duration =
+      config.minimize_corpus_database_new_crashes_duration;
 
   // Allow more crashes to be reported when running with FuzzTest. This allows
   // more unique crashes to collected after deduplication. But we don't want to
diff --git a/centipede/environment_test.cc b/centipede/environment_test.cc
index 8589e9e..ebf32ea 100644
--- a/centipede/environment_test.cc
+++ b/centipede/environment_test.cc
@@ -198,6 +198,7 @@
   config.replay_coverage_inputs = true;
   config.time_limit = absl::Seconds(10);
   config.time_budget_type = TimeBudgetType::kPerTest;
+  config.minimize_corpus_database_new_crashes_duration = absl::Seconds(123);
 
   env.UpdateWithTargetConfig(config);
 
@@ -209,6 +210,7 @@
   EXPECT_EQ(env.fuzztest_execution_id, "exec");
   EXPECT_TRUE(env.fuzztest_replay_coverage_inputs);
   EXPECT_EQ(env.fuzztest_time_limit_per_test, absl::Seconds(10));
+  EXPECT_EQ(env.fuzztest_minimize_new_crashes_duration, absl::Seconds(123));
 }
 
 TEST(Environment, UpdatesReplayOnlyConfiguration) {
diff --git a/centipede/minimize_crash.cc b/centipede/minimize_crash.cc
index 66ef9a5..d7feba5 100644
--- a/centipede/minimize_crash.cc
+++ b/centipede/minimize_crash.cc
@@ -26,6 +26,7 @@
 #include "absl/base/thread_annotations.h"
 #include "absl/container/flat_hash_set.h"
 #include "absl/synchronization/mutex.h"
+#include "absl/types/span.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/environment.h"
 #include "./centipede/mutation_data.h"
@@ -89,6 +90,7 @@
   auto callbacks = scoped_callback.callbacks();
   BatchResult batch_result;
 
+  const size_t max_attempts = std::max<size_t>(1, env.replay_crash_attempts);
   size_t num_batches = env.num_runs / env.batch_size;
   for (size_t i = 0; i < num_batches; ++i) {
     if (stop_condition.ShouldStop()) break;
@@ -127,22 +129,38 @@
     std::sort(smaller_mutants.begin(), smaller_mutants.end(),
               [](const auto& a, const auto& b) { return a.size() < b.size(); });
 
-    // Execute all mutants. If a new crasher is found, add it to `state`.
-    if (callbacks->Execute(env.binary, smaller_mutants, batch_result)) {
-      continue;
+    std::vector<ByteSpan> inputs_to_execute;
+    inputs_to_execute.reserve(smaller_mutants.size() * max_attempts);
+    for (const auto& m : smaller_mutants) {
+      inputs_to_execute.insert(inputs_to_execute.end(), max_attempts, m);
     }
 
-    if (batch_result.failure_signature() != crash_signature) {
-      continue;
-    }
+    // Execute all mutants in chunks of at most `env.batch_size`. If a new
+    // crasher is found, add it to `state`.
+    for (size_t offset = 0; offset < inputs_to_execute.size();
+         offset += env.batch_size) {
+      if (stop_condition.ShouldStop()) break;
+      const size_t chunk_size =
+          std::min(inputs_to_execute.size() - offset, env.batch_size);
+      const absl::Span<const ByteSpan> chunk =
+          absl::MakeConstSpan(inputs_to_execute).subspan(offset, chunk_size);
+      if (callbacks->Execute(env.binary, chunk, batch_result)) {
+        continue;
+      }
 
-    size_t crash_inputs_idx = batch_result.num_outputs_read();
-    FUZZTEST_CHECK_LT(crash_inputs_idx, smaller_mutants.size());
-    const auto& new_crasher = smaller_mutants[crash_inputs_idx];
-    FUZZTEST_LOG(INFO) << "Crasher: size: " << new_crasher.size() << ": "
-                       << AsPrintableString(new_crasher, /*max_len=*/40);
-    state.AddCrasher({new_crasher.begin(), new_crasher.end()},
-                     std::move(batch_result.failure_description()));
+      if (batch_result.failure_signature() != crash_signature) {
+        continue;
+      }
+
+      size_t crash_inputs_idx = batch_result.num_outputs_read();
+      FUZZTEST_CHECK_LT(crash_inputs_idx, chunk.size());
+      const auto& new_crasher = chunk[crash_inputs_idx];
+      FUZZTEST_LOG(INFO) << "Crasher: size: " << new_crasher.size() << ": "
+                         << AsPrintableString(new_crasher, /*max_len=*/40);
+      state.AddCrasher({new_crasher.begin(), new_crasher.end()},
+                       std::move(batch_result.failure_description()));
+      break;
+    }
   }
 }
 
diff --git a/centipede/minimize_crash_test.cc b/centipede/minimize_crash_test.cc
index 5b27047..6920065 100644
--- a/centipede/minimize_crash_test.cc
+++ b/centipede/minimize_crash_test.cc
@@ -14,6 +14,7 @@
 
 #include "./centipede/minimize_crash.h"
 
+#include <cstddef>
 #include <cstdlib>
 #include <string>
 #include <string_view>
@@ -40,15 +41,27 @@
 // A mock for CentipedeCallbacks.
 class MinimizerMock : public CentipedeCallbacks {
  public:
-  MinimizerMock(const Environment& env, StopCondition& stop_condition)
-      : CentipedeCallbacks(env, stop_condition) {}
+  MinimizerMock(const Environment& env, StopCondition& stop_condition,
+                size_t crash_on_attempt = 1)
+      : CentipedeCallbacks(env, stop_condition),
+        crash_on_attempt_(crash_on_attempt) {}
 
-  // Runs FuzzMe() on every input, imitates failure if FuzzMe() returns true.
+  // Runs FuzzMe() on every input, imitates failure if FuzzMe() returns true
+  // `crash_on_attempt_` consecutive times on the same input.
   bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
                BatchResult& batch_result) override {
+    EXPECT_LE(inputs.size(), env_.batch_size);
     batch_result.ClearAndResize(inputs.size());
-    for (auto input : inputs) {
-      if (FuzzMe(input)) {
+    for (size_t i = 0; i < inputs.size(); ++i) {
+      const auto& input = inputs[i];
+      if (input.data() == last_input_.data() &&
+          input.size() == last_input_.size()) {
+        ++current_attempts_;
+      } else {
+        last_input_ = input;
+        current_attempts_ = 1;
+      }
+      if (FuzzMe(input) && current_attempts_ >= crash_on_attempt_) {
         batch_result.exit_code() = EXIT_FAILURE;
         // Set signature differently to test signature matching behavior.
         batch_result.failure_signature() =
@@ -73,16 +86,26 @@
     }
     return true;
   }
+
+  size_t crash_on_attempt_ = 1;
+  ByteSpan last_input_;
+  size_t current_attempts_ = 0;
 };
 
 // Factory that creates/destroys MinimizerMock.
 class MinimizerMockFactory : public CentipedeCallbacksFactory {
  public:
+  explicit MinimizerMockFactory(size_t crash_on_attempt = 1)
+      : crash_on_attempt_(crash_on_attempt) {}
+
   CentipedeCallbacks* absl_nonnull create(
       const Environment& env, StopCondition& stop_condition) override {
-    return new MinimizerMock(env, stop_condition);
+    return new MinimizerMock(env, stop_condition, crash_on_attempt_);
   }
   void destroy(CentipedeCallbacks *cb) override { delete cb; }
+
+ private:
+  size_t crash_on_attempt_ = 1;
 };
 
 TEST(MinimizeTest, FailsWhenCrasherCannotBeMinimized) {
@@ -154,5 +177,27 @@
   EXPECT_THAT(minimized_crashers, UnorderedElementsAre(ByteArray{'z'}));
 }
 
+TEST(MinimizeTest, MinimizesWithReplayCrashAttempts) {
+  Environment env;
+  env.num_runs = 100000;
+  MinimizerMockFactory factory(/*crash_on_attempt=*/3);
+  StopCondition stop_condition;
+
+  ByteArray original_crasher = {'f', 'f', 'f', 'f', 'f', 'f',
+                                'z', 'z', 'z', 'z', 'z', 'z'};
+  env.replay_crash_attempts = 2;
+  EXPECT_FALSE(MinimizeCrash(original_crasher, env, factory, "signature one",
+                             stop_condition)
+                   .has_value());
+
+  env.replay_crash_attempts = 3;
+  auto result = MinimizeCrash(original_crasher, env, factory, "signature one",
+                              stop_condition);
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(result->input, ByteArray{'f'});
+  EXPECT_EQ(result->description, "description one");
+  EXPECT_FALSE(stop_condition.StopRequested());
+}
+
 }  // namespace
 }  // namespace fuzztest::internal
diff --git a/e2e_tests/corpus_database_test.cc b/e2e_tests/corpus_database_test.cc
index 3f80a9c..b8af0bd 100644
--- a/e2e_tests/corpus_database_test.cc
+++ b/e2e_tests/corpus_database_test.cc
@@ -152,9 +152,11 @@
     UpdateCorpusDatabaseTest::run_map_{};
 
 TEST_P(UpdateCorpusDatabaseTest, RunsFuzzTests) {
-  EXPECT_THAT_LOG(GetUpdateCorpusDatabaseStdErr(),
-                  AllOf(HasSubstr("Fuzzing FuzzTest.FailsInTwoWays"),
-                        HasSubstr("Fuzzing FuzzTest.FailsWithStackOverflow")));
+  EXPECT_THAT_LOG(
+      GetUpdateCorpusDatabaseStdErr(),
+      AllOf(HasSubstr("Fuzzing FuzzTest.FailsInTwoWays"),
+            HasSubstr("Fuzzing FuzzTest.FailsWithStackOverflow"),
+            HasSubstr("Fuzzing FuzzTest.FailsWithMinimizableCrashingSeed")));
 }
 
 TEST_P(UpdateCorpusDatabaseTest, UsesMultipleShardsForFuzzingAndDistillation) {
@@ -170,7 +172,8 @@
       GetUpdateCorpusDatabaseStdErr(),
       AllOf(ContainsRegex(R"re(Failure\s*: GoogleTest assertion failure)re"),
             ContainsRegex(R"re(Failure\s*: heap-buffer-overflow)re"),
-            ContainsRegex(R"re(Failure\s*: stack-limit-exceeded)re")));
+            ContainsRegex(R"re(Failure\s*: stack-limit-exceeded)re"),
+            ContainsRegex(R"re(Failure\s*: SIGABRT)re")));
 }
 
 TEST_P(UpdateCorpusDatabaseTest, DeduplicatesCrashes) {
@@ -190,6 +193,11 @@
                             R"re((?s)=== Summary of detected crashes ===
 .*?Fuzz test    : FuzzTest.FailsWithStackOverflow
 .*?Total crashes: 1
+.*?=== End of summary of detected crashes ===)re"),
+                        ContainsRegex(
+                            R"re((?s)=== Summary of detected crashes ===
+.*?Fuzz test    : FuzzTest.FailsWithMinimizableCrashingSeed
+.*?Total crashes: 1
 .*?=== End of summary of detected crashes ===)re")));
 }
 
@@ -209,6 +217,10 @@
                   HasSubstr("Fuzzing FuzzTest.FailsWithStackOverflow for 10s"));
   EXPECT_THAT_LOG(std_err,
                   Not(HasSubstr("Fuzzing FuzzTest.FailsInTwoWays for 10s")));
+  EXPECT_THAT_LOG(
+      std_err,
+      Not(HasSubstr(
+          "Fuzzing FuzzTest.FailsWithMinimizableCrashingSeed for 10s")));
 }
 
 TEST_P(UpdateCorpusDatabaseTest, StartsNewFuzzTestRunsWithoutExecutionIds) {
@@ -378,6 +390,10 @@
                         ContainsRegex(
                             R"re((?s)=== Summary of detected crashes ===
 .*?Fuzz test    : FuzzTest.FailsWithStackOverflow
+.*?=== End of summary of detected crashes ===)re"),
+                        ContainsRegex(
+                            R"re((?s)=== Summary of detected crashes ===
+.*?Fuzz test    : FuzzTest.FailsWithMinimizableCrashingSeed
 .*?=== End of summary of detected crashes ===)re")));
 }
 
@@ -491,6 +507,43 @@
   EXPECT_FALSE(fuzzing_time_file.ok()) << *fuzzing_time_file;
 }
 
+TEST_P(UpdateCorpusDatabaseTest, MinimizesNewCrashesWithFlagSet) {
+  TempDir corpus_database;
+
+  // 1st run that minimizes the new crash.
+  RunOptions fst_run_options;
+  fst_run_options.fuzztest_flags = {
+      {"corpus_database", corpus_database.path()},
+      {"fuzz", "FailsWithMinimizableCrashingSeed"},
+      {"fuzz_for", "5s"},
+      {"minimize_corpus_database_new_crashes_duration", "5s"},
+      {"execution_id", "some_execution_id_1"},
+  };
+  fst_run_options.timeout = absl::Seconds(20);
+  auto [fst_status_unused, fst_std_out_unused, fst_std_err] =
+      RunBinaryMaybeWithCentipede(GetCorpusDatabaseTestingBinaryPath(),
+                                  fst_run_options);
+
+  EXPECT_THAT_LOG(fst_std_err,
+                  HasSubstr("Starting the crash minimization loop"));
+
+  // 2nd run that should not trigger minimization due to no new crashes.
+  RunOptions snd_run_options;
+  snd_run_options.fuzztest_flags = {
+      {"corpus_database", corpus_database.path()},
+      {"fuzz", "FailsWithMinimizableCrashingSeed"},
+      {"fuzz_for", "5s"},
+      {"minimize_corpus_database_new_crashes_duration", "5s"},
+      {"execution_id", "some_execution_id_2"},
+  };
+  snd_run_options.timeout = absl::Seconds(20);
+  auto [snd_status_unused, snd_std_out_unused, snd_std_err] =
+      RunBinaryMaybeWithCentipede(GetCorpusDatabaseTestingBinaryPath(),
+                                  snd_run_options);
+  EXPECT_THAT_LOG(snd_std_err,
+                  Not(HasSubstr("Starting the crash minimization loop")));
+}
+
 INSTANTIATE_TEST_SUITE_P(
     UpdateCorpusDatabaseTestWithExecutionModel, UpdateCorpusDatabaseTest,
     testing::ValuesIn({
diff --git a/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc b/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc
index 8a4bdcc..4cd5020 100644
--- a/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc
+++ b/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include <cstddef>
+#include <cstdlib>
 
 #include "gtest/gtest.h"
 #include "./fuzztest/fuzztest.h"
@@ -56,4 +57,14 @@
 FUZZ_TEST(FuzzTest, FailsWithStackOverflow)
     .WithDomains(fuzztest::Just(kDepthToReachStackOverflow));
 
+// The failing seed can be minimized to "fuz".
+void FailsWithMinimizableCrashingSeed(const std::string& s) {
+  if (!s.empty() && s[0] == 'f' && s[s.size() >> 1] == 'u' &&
+      s[s.size() - 1] == 'z') {
+    std::abort();
+  }
+}
+FUZZ_TEST(FuzzTest, FailsWithMinimizableCrashingSeed)
+    .WithSeeds({"f.....u.....z"});
+
 }  // namespace
diff --git a/fuzztest/init_fuzztest.cc b/fuzztest/init_fuzztest.cc
index 74c1bc5..9d6c27b 100644
--- a/fuzztest/init_fuzztest.cc
+++ b/fuzztest/init_fuzztest.cc
@@ -135,6 +135,12 @@
     "value of --" FUZZTEST_FLAG_PREFIX "time_budget_type.");
 
 FUZZTEST_DEFINE_FLAG(
+    absl::Duration, minimize_corpus_database_new_crashes_duration,
+    absl::ZeroDuration(),
+    "When set to non-zero duration, specifies the duration to minimize newly "
+    "found crashes when updating the corpus database.");
+
+FUZZTEST_DEFINE_FLAG(
     fuzztest::internal::TimeBudgetType, time_budget_type,
     fuzztest::internal::TimeBudgetType::kPerTest,
     "Determines whether the time budget specified by --" FUZZTEST_FLAG_PREFIX
@@ -424,6 +430,8 @@
       absl::GetFlag(FUZZTEST_FLAG(time_limit_per_input)),
       time_limit,
       time_budget_type,
+      absl::GetFlag(
+          FUZZTEST_FLAG(minimize_corpus_database_new_crashes_duration)),
       num_jobs,
       absl::GetFlag(FUZZTEST_FLAG(internal_centipede_command)),
       absl::GetFlag(FUZZTEST_FLAG(internal_crashing_input_to_reproduce)),
diff --git a/fuzztest/internal/configuration.cc b/fuzztest/internal/configuration.cc
index c2de6ca..6b2e657 100644
--- a/fuzztest/internal/configuration.cc
+++ b/fuzztest/internal/configuration.cc
@@ -203,6 +203,8 @@
       absl::FormatDuration(time_limit_per_input);
   std::string time_limit_str = absl::FormatDuration(time_limit);
   std::string time_budget_type_str = AbslUnparseFlag(time_budget_type);
+  std::string minimize_corpus_database_new_crashes_duration_str =
+      absl::FormatDuration(minimize_corpus_database_new_crashes_duration);
   std::string out;
   out.resize(
       SpaceFor(corpus_database) + SpaceFor(stats_root) +
@@ -216,6 +218,7 @@
       SpaceFor(subprocess_cleanup_timeout_str) + SpaceFor(stack_limit) +
       SpaceFor(rss_limit) + SpaceFor(time_limit_per_input_str) +
       SpaceFor(time_limit_str) + SpaceFor(time_budget_type_str) +
+      SpaceFor(minimize_corpus_database_new_crashes_duration_str) +
       SpaceFor(jobs) + SpaceFor(centipede_command) +
       SpaceFor(crashing_input_to_reproduce) + SpaceFor(replay_crash_attempts) +
       SpaceFor(reproduction_command_template));
@@ -240,6 +243,8 @@
   offset = WriteString(out, offset, time_limit_per_input_str);
   offset = WriteString(out, offset, time_limit_str);
   offset = WriteString(out, offset, time_budget_type_str);
+  offset = WriteString(out, offset,
+                       minimize_corpus_database_new_crashes_duration_str);
   offset = WriteIntegral(out, offset, jobs);
   offset = WriteOptionalString(out, offset, centipede_command);
   offset = WriteOptionalString(out, offset, crashing_input_to_reproduce);
@@ -274,6 +279,8 @@
     ASSIGN_OR_RETURN(time_limit_per_input_str, ConsumeString(serialized));
     ASSIGN_OR_RETURN(time_limit_str, ConsumeString(serialized));
     ASSIGN_OR_RETURN(time_budget_type_str, ConsumeString(serialized));
+    ASSIGN_OR_RETURN(minimize_corpus_database_new_crashes_duration_str,
+                     ConsumeString(serialized));
     ASSIGN_OR_RETURN(jobs, Consume<size_t>(serialized));
     ASSIGN_OR_RETURN(centipede_command, ConsumeOptionalString(serialized));
     ASSIGN_OR_RETURN(crashing_input_to_reproduce,
@@ -292,6 +299,9 @@
     ASSIGN_OR_RETURN(time_limit, ParseDuration(*time_limit_str));
     ASSIGN_OR_RETURN(time_budget_type,
                      ParseTimeBudgetType(*time_budget_type_str));
+    ASSIGN_OR_RETURN(
+        minimize_corpus_database_new_crashes_duration,
+        ParseDuration(*minimize_corpus_database_new_crashes_duration_str));
     return Configuration{*std::move(corpus_database),
                          *std::move(stats_root),
                          *std::move(workdir_root),
@@ -312,6 +322,7 @@
                          *time_limit_per_input,
                          *time_limit,
                          *time_budget_type,
+                         *minimize_corpus_database_new_crashes_duration,
                          *jobs,
                          *std::move(centipede_command),
                          *std::move(crashing_input_to_reproduce),
diff --git a/fuzztest/internal/configuration.h b/fuzztest/internal/configuration.h
index 0aceee9..b40577d 100644
--- a/fuzztest/internal/configuration.h
+++ b/fuzztest/internal/configuration.h
@@ -99,6 +99,8 @@
   absl::Duration time_limit = absl::InfiniteDuration();
   // Whether the time limit is for each test or for all tests in the binary.
   TimeBudgetType time_budget_type = TimeBudgetType::kPerTest;
+  // The duration for minimizing new crashes when updating corpus database.
+  absl::Duration minimize_corpus_database_new_crashes_duration;
   // The number of fuzzing jobs to run in parallel. Zero indicates that the
   // number of jobs is unspecified by the test binary.
   size_t jobs = 0;
diff --git a/fuzztest/internal/configuration_test.cc b/fuzztest/internal/configuration_test.cc
index a2ccf21..16cf440 100644
--- a/fuzztest/internal/configuration_test.cc
+++ b/fuzztest/internal/configuration_test.cc
@@ -36,6 +36,8 @@
          config.time_limit_per_input == other->time_limit_per_input &&
          config.time_limit == other->time_limit &&
          config.time_budget_type == other->time_budget_type &&
+         config.minimize_corpus_database_new_crashes_duration ==
+             other->minimize_corpus_database_new_crashes_duration &&
          config.jobs == other->jobs &&
          config.centipede_command == other->centipede_command &&
          config.crashing_input_to_reproduce ==
@@ -47,31 +49,33 @@
 
 TEST(ConfigurationTest,
      DeserializeYieldsSerializedConfigurationWithoutOptionalValues) {
-  Configuration configuration{"corpus_database",
-                              "stats_root",
-                              "workdir_root",
-                              "binary_identifier",
-                              /*fuzz_tests=*/{},
-                              /*fuzz_tests_in_current_shard=*/{},
-                              /*continue_after_crash=*/true,
-                              /*reproduce_findings_as_separate_tests=*/true,
-                              /*replay_coverage_inputs=*/true,
-                              /*only_replay=*/true,
-                              /*update_corpus_database=*/true,
-                              /*replay_in_single_process=*/true,
-                              "execution_id",
-                              /*print_subprocess_log=*/true,
-                              /*subprocess_cleanup_time=*/absl::Seconds(42),
-                              /*stack_limit=*/100,
-                              /*rss_limit=*/200,
-                              /*time_limit_per_input=*/absl::Seconds(42),
-                              /*time_limit=*/absl::Minutes(42),
-                              /*time_budget_type=*/TimeBudgetType::kPerTest,
-                              /*jobs=*/1,
-                              /*centipede_command=*/std::nullopt,
-                              /*crashing_input_to_reproduce=*/std::nullopt,
-                              /*replay_crash_attempts=*/5,
-                              /*reproduction_command_template=*/std::nullopt};
+  Configuration configuration{
+      "corpus_database",
+      "stats_root",
+      "workdir_root",
+      "binary_identifier",
+      /*fuzz_tests=*/{},
+      /*fuzz_tests_in_current_shard=*/{},
+      /*continue_after_crash=*/true,
+      /*reproduce_findings_as_separate_tests=*/true,
+      /*replay_coverage_inputs=*/true,
+      /*only_replay=*/true,
+      /*update_corpus_database=*/true,
+      /*replay_in_single_process=*/true,
+      "execution_id",
+      /*print_subprocess_log=*/true,
+      /*subprocess_cleanup_time=*/absl::Seconds(42),
+      /*stack_limit=*/100,
+      /*rss_limit=*/200,
+      /*time_limit_per_input=*/absl::Seconds(42),
+      /*time_limit=*/absl::Minutes(42),
+      /*time_budget_type=*/TimeBudgetType::kPerTest,
+      /*minimize_corpus_database_new_crashes_duration=*/absl::Seconds(123),
+      /*jobs=*/1,
+      /*centipede_command=*/std::nullopt,
+      /*crashing_input_to_reproduce=*/std::nullopt,
+      /*replay_crash_attempts=*/5,
+      /*reproduction_command_template=*/std::nullopt};
 
   EXPECT_THAT(Configuration::Deserialize(configuration.Serialize()),
               IsOkAndEquals(configuration));
@@ -79,31 +83,33 @@
 
 TEST(ConfigurationTest,
      DeserializeYieldsSerializedConfigurationWithOptionalValues) {
-  Configuration configuration{"corpus_database",
-                              "stats_root",
-                              "workdir_root",
-                              "binary_identifier",
-                              {"FuzzTest1", "FuzzTest2"},
-                              {"FuzzTest1"},
-                              /*continue_after_crash=*/true,
-                              /*reproduce_findings_as_separate_tests=*/true,
-                              /*replay_coverage_inputs=*/true,
-                              /*only_replay=*/true,
-                              /*update_corpus_database=*/true,
-                              /*replay_in_single_process=*/true,
-                              "execution_id",
-                              /*print_subprocess_log=*/true,
-                              /*subprocess_cleanup_time=*/absl::Seconds(42),
-                              /*stack_limit=*/100,
-                              /*rss_limit=*/200,
-                              /*time_limit_per_input=*/absl::Seconds(42),
-                              /*time_limit=*/absl::Minutes(42),
-                              /*time_budget_type=*/TimeBudgetType::kPerTest,
-                              /*jobs=*/1,
-                              "centipede_command",
-                              "crashing_input_to_reproduce",
-                              /*replay_crash_attempts=*/20,
-                              "reproduction_command_template"};
+  Configuration configuration{
+      "corpus_database",
+      "stats_root",
+      "workdir_root",
+      "binary_identifier",
+      {"FuzzTest1", "FuzzTest2"},
+      {"FuzzTest1"},
+      /*continue_after_crash=*/true,
+      /*reproduce_findings_as_separate_tests=*/true,
+      /*replay_coverage_inputs=*/true,
+      /*only_replay=*/true,
+      /*update_corpus_database=*/true,
+      /*replay_in_single_process=*/true,
+      "execution_id",
+      /*print_subprocess_log=*/true,
+      /*subprocess_cleanup_time=*/absl::Seconds(42),
+      /*stack_limit=*/100,
+      /*rss_limit=*/200,
+      /*time_limit_per_input=*/absl::Seconds(42),
+      /*time_limit=*/absl::Minutes(42),
+      /*time_budget_type=*/TimeBudgetType::kPerTest,
+      /*minimize_corpus_database_new_crashes_duration=*/absl::Seconds(123),
+      /*jobs=*/1,
+      "centipede_command",
+      "crashing_input_to_reproduce",
+      /*replay_crash_attempts=*/20,
+      "reproduction_command_template"};
 
   EXPECT_THAT(Configuration::Deserialize(configuration.Serialize()),
               IsOkAndEquals(configuration));