fuzztest-rust | add continue_after_crash support to fuzzing and replay-corpus modes

Add support for the --continue-after-crash flag and FUZZTEST_CONTINUE_AFTER_CRASH environment variable to FuzzTest Rust.

- By default (continue_after_crash = false), pass --exit_on_crash to Centipede so fuzzing and corpus replay stop immediately on the first crash.
- When continue_after_crash is set to true, allow fuzzing or corpus replay to continue until the time limit is reached.
- Forward the configuration in cargo-fuzztest to child test processes.
- Update tests to verify exit-on-crash behavior and continued execution across all layers.

PiperOrigin-RevId: 967312238
diff --git a/rust/cargo_fuzztest/src/lib.rs b/rust/cargo_fuzztest/src/lib.rs
index 353e26f..bdfa67f 100644
--- a/rust/cargo_fuzztest/src/lib.rs
+++ b/rust/cargo_fuzztest/src/lib.rs
@@ -100,6 +100,7 @@
                             .fuzz_for
                             .unwrap_or(RunDuration::Indefinitely),
                         jobs: self.fuzztest_options.jobs,
+                        continue_after_crash: self.fuzztest_options.continue_after_crash,
                     })
                 } else {
                     mode
@@ -248,11 +249,14 @@
             }
 
             ExecutionMode::Fuzz(fuzz_options) => {
-                let FuzzOptions { fuzz_for, jobs } = fuzz_options;
+                let FuzzOptions { fuzz_for, jobs, continue_after_crash } = fuzz_options;
                 cmd.env("FUZZTEST_FUZZ_FOR", fuzz_for.to_string());
                 if let Some(jobs) = jobs {
                     cmd.env("FUZZTEST_JOBS", jobs.to_string());
                 }
+                if continue_after_crash {
+                    cmd.env("FUZZTEST_CONTINUE_AFTER_CRASH", "true");
+                }
             }
 
             ExecutionMode::ReplayCrash(replay_options) => {
@@ -273,6 +277,9 @@
                     TimeBudgetType::Total => "total",
                 };
                 cmd.env("FUZZTEST_TIME_BUDGET_TYPE", time_budget_str);
+                if replay_corpus_options.continue_after_crash {
+                    cmd.env("FUZZTEST_CONTINUE_AFTER_CRASH", "true");
+                }
             }
 
             ExecutionMode::ListCrashIds(list_crash_ids_options) => {
@@ -609,6 +616,7 @@
                 replay_corpus_for: "10s".parse().expect("valid duration string"),
                 time_budget_type: TimeBudgetType::PerTest,
                 jobs: None,
+                continue_after_crash: false,
             })
         );
     }
@@ -637,6 +645,7 @@
                 replay_corpus_for: RunDuration::Indefinitely,
                 time_budget_type: TimeBudgetType::PerTest,
                 jobs: None,
+                continue_after_crash: false,
             })
         );
     }
@@ -662,6 +671,7 @@
                 replay_corpus_for: RunDuration::Indefinitely,
                 time_budget_type: TimeBudgetType::PerTest,
                 jobs: None,
+                continue_after_crash: false,
             })
         );
     }
@@ -691,6 +701,7 @@
                 replay_corpus_for: "10s".parse().unwrap(),
                 time_budget_type: TimeBudgetType::Total,
                 jobs: None,
+                continue_after_crash: false,
             })
         );
     }
@@ -879,4 +890,86 @@
             Some("/custom/centipede".to_string())
         )));
     }
+
+    #[gtest]
+    fn test_cli_option_parsing_continue_after_crash_flag() {
+        let parsed = CargoFuzzTestOptions::try_parse_from([
+            "cargo-fuzztest",
+            "--fuzz-for",
+            "5s",
+            "--continue-after-crash",
+            "--centipede-binary-path",
+            "/custom/centipede",
+        ])
+        .unwrap();
+
+        assert!(parsed.fuzztest_options.continue_after_crash);
+        let mode = parsed.execution_mode().unwrap();
+        let expected_duration = "5s".parse().unwrap();
+        assert_eq!(
+            mode,
+            ExecutionMode::Fuzz(FuzzOptions {
+                fuzz_for: RunDuration::Fixed(expected_duration),
+                jobs: None,
+                continue_after_crash: true,
+            })
+        );
+    }
+
+    #[gtest]
+    fn test_build_run_command_fuzz_with_continue_after_crash() {
+        let options = CargoFuzzTestOptions {
+            fuzztest_options: FuzzTestOptions {
+                fuzz_for: Some("5s".parse().unwrap()),
+                continue_after_crash: true,
+                ..Default::default()
+            },
+            centipede_binary_path: Some("/custom/centipede".to_string()),
+            ..Default::default()
+        };
+        let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+        let cmd =
+            runner.build_run_command(Path::new("/tmp/test_bin")).expect("should build run command");
+
+        let envs: Vec<(String, Option<String>)> = cmd
+            .get_envs()
+            .map(|(k, v)| {
+                (k.to_string_lossy().to_string(), v.map(|s| s.to_string_lossy().to_string()))
+            })
+            .collect();
+
+        assert!(
+            envs.contains(&("FUZZTEST_CONTINUE_AFTER_CRASH".to_string(), Some("true".to_string())))
+        );
+        assert!(envs.contains(&("FUZZTEST_FUZZ_FOR".to_string(), Some("5s".to_string()))));
+    }
+
+    #[gtest]
+    fn test_build_run_command_replay_corpus_with_continue_after_crash() {
+        let options = CargoFuzzTestOptions {
+            fuzztest_options: FuzzTestOptions {
+                replay_corpus_for: Some("10s".parse().expect("valid duration")),
+                corpus_db: Some("/tmp/corpus_db".into()),
+                continue_after_crash: true,
+                ..Default::default()
+            },
+            centipede_binary_path: Some("/custom/centipede".to_string()),
+            ..Default::default()
+        };
+        let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+        let cmd =
+            runner.build_run_command(Path::new("/tmp/test_bin")).expect("should build run command");
+
+        let envs: Vec<(String, Option<String>)> = cmd
+            .get_envs()
+            .map(|(k, v)| {
+                (k.to_string_lossy().to_string(), v.map(|s| s.to_string_lossy().to_string()))
+            })
+            .collect();
+
+        assert!(
+            envs.contains(&("FUZZTEST_CONTINUE_AFTER_CRASH".to_string(), Some("true".to_string())))
+        );
+        assert!(envs.contains(&("FUZZTEST_REPLAY_CORPUS_FOR".to_string(), Some("10s".to_string()))));
+    }
 }
diff --git a/rust/cargo_fuzztest/tests/e2e_cli_test.rs b/rust/cargo_fuzztest/tests/e2e_cli_test.rs
index 04ed647..b5a8717 100644
--- a/rust/cargo_fuzztest/tests/e2e_cli_test.rs
+++ b/rust/cargo_fuzztest/tests/e2e_cli_test.rs
@@ -165,6 +165,7 @@
     let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path());
     cmd.arg(test_target)
         .arg("--fuzz-for=5s")
+        .arg("--continue-after-crash")
         .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH")
         .arg("--centipede-binary-path")
         .arg(&centipede_bin)
@@ -248,6 +249,7 @@
     let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path());
     cmd.arg(test_target)
         .arg("--fuzz-for=5s")
+        .arg("--continue-after-crash")
         .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH")
         .arg("--centipede-binary-path")
         .arg(&centipede_bin)
@@ -412,6 +414,7 @@
     let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path());
     cmd.arg(test_target)
         .arg("--fuzz-for=5s")
+        .arg("--continue-after-crash")
         .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH")
         .arg("--centipede-binary-path")
         .arg(&centipede_bin)
@@ -448,3 +451,29 @@
     // good check here?
     expect_true!(!crash_ids.is_empty());
 }
+
+#[gtest]
+fn test_cargo_fuzztest_e2e_continue_after_crash() {
+    let sample_crate_path = get_sample_crate_path("another_sample_fuzz_crate");
+    let test_target = "__fuzztest_mod__crashing_fuzztest_target::crashing_fuzztest_target";
+
+    let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory");
+    let centipede_bin = env::var("FUZZTEST_CENTIPEDE_BINARY_PATH")
+        .expect("FUZZTEST_CENTIPEDE_BINARY_PATH needs to be set for the test");
+
+    let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path());
+    cmd.arg(test_target)
+        .arg("--fuzz-for=3s")
+        .arg("--continue-after-crash")
+        .env_remove("FUZZTEST_CENTIPEDE_BINARY_PATH")
+        .arg("--centipede-binary-path")
+        .arg(&centipede_bin)
+        .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true");
+
+    let output = cmd.output().expect("Failed to run cargo-fuzztest with continue-after-crash");
+    let stderr_str = String::from_utf8_lossy(&output.stderr);
+
+    expect_true!(output.status.success());
+    expect_true!(stderr_str.contains("Property function ran but crashed."));
+    expect_true!(stderr_str.contains("Crashing bug found!"));
+}
diff --git a/rust/cargo_fuzztest/tests/runner_test.rs b/rust/cargo_fuzztest/tests/runner_test.rs
index a7c81c9..2a22451 100644
--- a/rust/cargo_fuzztest/tests/runner_test.rs
+++ b/rust/cargo_fuzztest/tests/runner_test.rs
@@ -33,7 +33,7 @@
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
     };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let args: Vec<String> = cmd.get_args().map(|s| s.to_string_lossy().to_string()).collect();
@@ -52,7 +52,7 @@
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
     };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let envs: Vec<(String, Option<String>)> = cmd
@@ -72,7 +72,7 @@
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
     };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let envs: Vec<(String, Option<String>)> = cmd
@@ -89,7 +89,7 @@
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
     };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let envs: Vec<(String, Option<String>)> = cmd
@@ -115,7 +115,7 @@
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
     };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let envs: Vec<(String, Option<String>)> = cmd
@@ -131,7 +131,7 @@
     let binary_path = get_sample_test_bin_path("sample_fuzz_crate");
     let fuzztest_options = FuzzTestOptions { jobs: Some(4), ..Default::default() };
     let options = CargoFuzzTestOptions { fuzztest_options, ..Default::default() };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let envs: Vec<(String, Option<String>)> = cmd
@@ -155,7 +155,7 @@
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
     };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let envs: Vec<(String, Option<String>)> = cmd
@@ -217,7 +217,7 @@
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
     };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let envs: Vec<(String, Option<String>)> = cmd
@@ -263,7 +263,7 @@
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
     };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let envs: Vec<(String, Option<String>)> = cmd
@@ -300,7 +300,7 @@
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
     };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let envs: Vec<(String, Option<String>)> = cmd
@@ -367,7 +367,7 @@
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
     };
-    let runner = FuzztestRunner::new("x86_64-unknown-linux-gnu".to_string(), options);
+    let runner = FuzztestRunner::new("sample-host-triple".to_string(), options);
     let cmd = runner.build_run_command(&binary_path).expect("valid run command");
 
     let envs: Vec<(String, Option<String>)> = cmd
diff --git a/rust/e2e_tests/replay_test.rs b/rust/e2e_tests/replay_test.rs
index d14d772..80c37ed 100644
--- a/rust/e2e_tests/replay_test.rs
+++ b/rust/e2e_tests/replay_test.rs
@@ -48,6 +48,7 @@
         .arg(test_name)
         .arg("--exact")
         .env("FUZZTEST_FUZZ_FOR", "15s")
+        .env("FUZZTEST_CONTINUE_AFTER_CRASH", "true")
         .env("FUZZTEST_CORPUS_DB", &db_dir)
         .env("FUZZTEST_WORKDIR_ROOT", &workdir_root_dir)
         .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
@@ -127,6 +128,7 @@
         .arg(test_name)
         .arg("--exact")
         .env("FUZZTEST_FUZZ_FOR", "15s")
+        .env("FUZZTEST_CONTINUE_AFTER_CRASH", "true")
         .env("FUZZTEST_CORPUS_DB", &db_dir)
         .env("FUZZTEST_WORKDIR_ROOT", &workdir_root_dir)
         .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
diff --git a/rust/e2e_tests/standalone_mode_test.rs b/rust/e2e_tests/standalone_mode_test.rs
index 53adb48..f328388 100644
--- a/rust/e2e_tests/standalone_mode_test.rs
+++ b/rust/e2e_tests/standalone_mode_test.rs
@@ -95,6 +95,8 @@
     let output = process.wait_with_output().expect("Should terminate");
     let stderr = String::from_utf8_lossy(&output.stderr);
 
+    // By default continue_after_crash is false, so finding a crash causes test failure.
+    expect_false!(output.status.success());
     expect_that!(stderr, matchers::contains_substring("Property function ran but crashed."));
     expect_that!(stderr, matchers::contains_regex("Signature[ \t]*: Unwinding panic"));
     // Centipede prefixes logs from the crashing worker with "CRASH LOG: ".
@@ -102,6 +104,31 @@
 }
 
 #[gtest]
+fn standalone_mode_continues_after_crash_when_enabled(fixture: &EnvVars) {
+    let test_name = "__fuzztest_mod__find_bug_fuzz_test::find_bug_fuzz_test";
+
+    let process = Command::new(&fixture.target_binary_path)
+        .arg(test_name)
+        .env("FUZZTEST_FUZZ_FOR", "15s")
+        .env("FUZZTEST_CONTINUE_AFTER_CRASH", "true")
+        .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true")
+        .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path)
+        .env("RUST_TEST_NOCAPTURE", "1")
+        .stdout(std::process::Stdio::piped())
+        .stderr(std::process::Stdio::piped())
+        .spawn()
+        .expect("Failed to spawn binary");
+
+    let output = process.wait_with_output().expect("Should terminate");
+    let stderr = String::from_utf8_lossy(&output.stderr);
+
+    // With continue_after_crash=true, the fuzz test runs for the full time limit and exits cleanly.
+    expect_true!(output.status.success());
+    expect_that!(stderr, matchers::contains_substring("Property function ran but crashed."));
+    expect_that!(stderr, matchers::contains_substring("CRASH LOG: Bug found!"));
+}
+
+#[gtest]
 fn standalone_mode_spawns_parallel_jobs(fixture: &EnvVars) {
     let target_binary_path =
         fixture.target_binary_path.parent().unwrap().join("standalone_fuzz_tests_bin");
diff --git a/rust/options/src/lib.rs b/rust/options/src/lib.rs
index 5abe89c..6f6eabc 100644
--- a/rust/options/src/lib.rs
+++ b/rust/options/src/lib.rs
@@ -122,6 +122,19 @@
     /// regression, and crashing inputs for each test binary and fuzz test.
     #[arg(env = "FUZZTEST_CORPUS_DB", long, value_parser = parse_corpus_db_path)]
     pub corpus_db: Option<PathBuf>,
+
+    /// Controls the fuzzing and corpus replaying behavior when a crashing input is found.
+    ///
+    /// If set to false (default), the test execution stops upon finding the first crashing input,
+    /// and the test fails. If set to true, the execution logs any crash inputs found and continues
+    /// until reaching the time limit or manually stopped.
+    ///
+    /// Note that this does not affect crash replaying:
+    /// - Replaying a single crash (`--replay-id`) always stops immediately when a crash occurs.
+    /// - Replaying all crashes (`--replay-findings`) always continues replaying all remaining
+    ///   crashing inputs regardless of this flag.
+    #[arg(env = "FUZZTEST_CONTINUE_AFTER_CRASH", long)]
+    pub continue_after_crash: bool,
 }
 
 /// Strongly-typed domain execution mode for test runs.
@@ -172,6 +185,7 @@
                 replay_corpus_for,
                 time_budget_type: options.time_budget_type,
                 jobs: options.jobs,
+                continue_after_crash: options.continue_after_crash,
             });
         }
 
@@ -185,7 +199,11 @@
 
         // Continuous fuzzing mode is selected if an explicit duration/budget (`fuzz_for`) is specified.
         if let Some(fuzz_for) = &options.fuzz_for {
-            return ExecutionMode::Fuzz(FuzzOptions { fuzz_for: *fuzz_for, jobs: options.jobs });
+            return ExecutionMode::Fuzz(FuzzOptions {
+                fuzz_for: *fuzz_for,
+                jobs: options.jobs,
+                continue_after_crash: options.continue_after_crash,
+            });
         }
 
         ExecutionMode::SmokeTest
@@ -200,6 +218,8 @@
     /// If `jobs` is `None`, we won't specify the number of jobs while invoking Centipede and it
     /// will use its own default value.
     pub jobs: Option<usize>,
+
+    pub continue_after_crash: bool,
 }
 
 /// The duration or limit for fuzzing or replaying corpus.
@@ -251,6 +271,7 @@
     /// If `jobs` is `None`, we won't specify the number of jobs while invoking Centipede and it
     /// will use its own default value.
     pub jobs: Option<usize>,
+    pub continue_after_crash: bool,
 }
 
 /// Mode-specific options for listing crash IDs from the database.
@@ -376,6 +397,7 @@
             eq(&ExecutionMode::Fuzz(FuzzOptions {
                 fuzz_for: RunDuration::Fixed(expected_duration),
                 jobs: Some(4),
+                continue_after_crash: false,
             }))
         );
 
@@ -405,6 +427,7 @@
                 replay_corpus_for: expected_duration,
                 time_budget_type: TimeBudgetType::PerTest,
                 jobs: Some(4),
+                continue_after_crash: false,
             }))
         );
 
@@ -417,6 +440,63 @@
     }
 
     #[gtest]
+    fn test_continue_after_crash_with_fuzz_for_parsing_env() {
+        // SAFETY: Testing environment parsing in single-threaded context.
+        unsafe {
+            std::env::set_var("FUZZTEST_FUZZ_FOR", "5s");
+            std::env::set_var("FUZZTEST_CONTINUE_AFTER_CRASH", "true");
+        }
+
+        let options = FuzzTestOptions::parse_from(std::iter::empty::<OsString>());
+        expect_true!(options.continue_after_crash);
+        let expected_duration = "5s".parse().expect("valid duration");
+        expect_that!(
+            ExecutionMode::from_fuzztest_options(&options),
+            eq(&ExecutionMode::Fuzz(FuzzOptions {
+                fuzz_for: RunDuration::Fixed(expected_duration),
+                jobs: None,
+                continue_after_crash: true,
+            }))
+        );
+
+        // SAFETY: Cleaning up environment variables.
+        unsafe {
+            std::env::remove_var("FUZZTEST_FUZZ_FOR");
+            std::env::remove_var("FUZZTEST_CONTINUE_AFTER_CRASH");
+        }
+    }
+
+    #[gtest]
+    fn test_continue_after_crash_with_replay_corpus_parsing_env() {
+        // SAFETY: Testing environment parsing in single-threaded context.
+        unsafe {
+            std::env::set_var("FUZZTEST_REPLAY_CORPUS_FOR", "10s");
+            std::env::set_var("FUZZTEST_CORPUS_DB", "/tmp/corpus_db");
+            std::env::set_var("FUZZTEST_CONTINUE_AFTER_CRASH", "true");
+        }
+
+        let options = FuzzTestOptions::parse_from(std::iter::empty::<OsString>());
+        expect_true!(options.continue_after_crash);
+        let expected_duration = "10s".parse().expect("valid duration string");
+        expect_that!(
+            ExecutionMode::from_fuzztest_options(&options),
+            eq(&ExecutionMode::ReplayCorpus(ReplayCorpusOptions {
+                replay_corpus_for: expected_duration,
+                time_budget_type: TimeBudgetType::PerTest,
+                jobs: None,
+                continue_after_crash: true,
+            }))
+        );
+
+        // SAFETY: Cleaning up environment variables.
+        unsafe {
+            std::env::remove_var("FUZZTEST_REPLAY_CORPUS_FOR");
+            std::env::remove_var("FUZZTEST_CORPUS_DB");
+            std::env::remove_var("FUZZTEST_CONTINUE_AFTER_CRASH");
+        }
+    }
+
+    #[gtest]
     fn test_replay_findings_requires_corpus_db() {
         // SAFETY: Testing environment parsing in single-threaded context.
         unsafe {
diff --git a/rust/src/options.rs b/rust/src/options.rs
index b5c61d2..9f49718 100644
--- a/rust/src/options.rs
+++ b/rust/src/options.rs
@@ -229,6 +229,9 @@
                 if let Some(jobs) = &fuzz_opts.jobs {
                     add_arg(format!("--j={jobs}"))?;
                 }
+                if !fuzz_opts.continue_after_crash {
+                    add_arg("--exit_on_crash".to_string())?;
+                }
             }
             ExecutionMode::ReplayCrash(replay_opts) => {
                 add_arg("--replay_crash".to_string())?;
@@ -270,6 +273,9 @@
                 if let Some(jobs) = &replay_corpus_opts.jobs {
                     add_arg(format!("--j={jobs}"))?;
                 }
+                if !replay_corpus_opts.continue_after_crash {
+                    add_arg("--exit_on_crash".to_string())?;
+                }
             }
             ExecutionMode::ListCrashIds(list_opts) => {
                 add_arg("--list_crash_ids=true".to_string())?;
@@ -876,4 +882,66 @@
         expect_true!(args_str.iter().any(|s| s.starts_with("--workdir=")));
         expect_true!(args_str.contains(&"--test_name=my_mod.my_test"));
     }
+
+    #[gtest]
+    fn test_determine_execution_action_fuzz_continue_after_crash_false() {
+        let options = FuzzTestOptions {
+            fuzz_for: Some("5s".parse().unwrap()),
+            continue_after_crash: false,
+            ..Default::default()
+        };
+        let action = determine_execution_action_internal(&options, "my_mod::my_test");
+        let ExecutionAction::Standalone(args) = action else {
+            panic!("Expected Standalone action");
+        };
+        let args_str: Vec<&str> = args._c_strings.iter().map(|s| s.to_str().unwrap()).collect();
+        expect_true!(args_str.contains(&"--exit_on_crash"));
+    }
+
+    #[gtest]
+    fn test_determine_execution_action_fuzz_continue_after_crash_true() {
+        let options = FuzzTestOptions {
+            fuzz_for: Some("5s".parse().unwrap()),
+            continue_after_crash: true,
+            ..Default::default()
+        };
+        let action = determine_execution_action_internal(&options, "my_mod::my_test");
+        let ExecutionAction::Standalone(args) = action else {
+            panic!("Expected Standalone action");
+        };
+        let args_str: Vec<&str> = args._c_strings.iter().map(|s| s.to_str().unwrap()).collect();
+        expect_false!(args_str.contains(&"--exit_on_crash"));
+    }
+
+    #[gtest]
+    fn test_determine_execution_action_replay_corpus_continue_after_crash_false() {
+        let options = FuzzTestOptions {
+            replay_corpus_for: Some("5s".parse().unwrap()),
+            continue_after_crash: false,
+            corpus_db: Some("/tmp/corpus_db".into()),
+            ..Default::default()
+        };
+        let action = determine_execution_action_internal(&options, "my_mod::my_test");
+        let ExecutionAction::Standalone(args) = action else {
+            panic!("Expected Standalone action");
+        };
+        let args_str: Vec<&str> = args._c_strings.iter().map(|s| s.to_str().unwrap()).collect();
+        expect_true!(args_str.contains(&"--exit_on_crash"));
+    }
+
+    #[gtest]
+    fn test_determine_execution_action_replay_corpus_continue_after_crash_true() {
+        let options = FuzzTestOptions {
+            replay_corpus_for: Some("5s".parse().unwrap()),
+            continue_after_crash: true,
+            corpus_db: Some("/tmp/corpus_db".into()),
+            ..Default::default()
+        };
+        let action = determine_execution_action_internal(&options, "my_mod::my_test");
+        let ExecutionAction::Standalone(args) = action else {
+            panic!("Expected Standalone action");
+        };
+        let args_str: Vec<&str> = args._c_strings.iter().map(|s| s.to_str().unwrap()).collect();
+        expect_false!(args_str.contains(&"--exit_on_crash"));
+    }
 }