cargo-fuzztest: support replaying a crash by id

- Validate that --centipede-binary-path and --corpus-db are provided when replaying a crash.
- Pass FUZZTEST_REPLAY_ID and FUZZTEST_CORPUS_DB environment variables to the target binary.
- Add unit and integration tests for crash replay configuration.

PiperOrigin-RevId: 965984007
diff --git a/rust/cargo_fuzztest/BUILD b/rust/cargo_fuzztest/BUILD
index 2b2fae5..d9ff6a5 100644
--- a/rust/cargo_fuzztest/BUILD
+++ b/rust/cargo_fuzztest/BUILD
@@ -62,7 +62,7 @@
 )
 
 rust_test(
-    name = "sample_fuzz_test_bin",
+    name = "sample_fuzz_crate_bin",
     srcs = ["test_crates/sample_fuzz_crate/src/lib.rs"],
     edition = "2024",
     tags = [
@@ -77,9 +77,12 @@
 
 rust_test(
     name = "runner_test",
-    srcs = ["tests/runner_test.rs"],
+    srcs = [
+        "tests/common/mod.rs",
+        "tests/runner_test.rs",
+    ],
     data = [
-        ":sample_fuzz_test_bin",
+        ":sample_fuzz_crate_bin",
     ],
     edition = "2024",
     deps = [
diff --git a/rust/cargo_fuzztest/Cargo.toml b/rust/cargo_fuzztest/Cargo.toml
index 990523c..0a663e1 100644
--- a/rust/cargo_fuzztest/Cargo.toml
+++ b/rust/cargo_fuzztest/Cargo.toml
@@ -30,11 +30,24 @@
 tempfile = "3.27.0"
 
 # Compiled as a [[test]] target so Cargo builds it with the test harness enabled
-# (`rustc --test`) and exposes `CARGO_BIN_EXE_sample_fuzz_test_bin` to `runner_test`.
+# (`rustc --test`) and exposes `CARGO_BIN_EXE_sample_fuzz_crate_bin` to `runner_test`.
 [[test]]
-name = "sample_fuzz_test_bin"
+name = "sample_fuzz_crate_bin"
 path = "test_crates/sample_fuzz_crate/src/lib.rs"
+test = false
+
+# Compiled as a [[test]] target so Cargo builds it with the test harness enabled
+# (`rustc --test`) and exposes `CARGO_BIN_EXE_another_sample_fuzz_crate_bin` to `runner_test`.
+[[test]]
+name = "another_sample_fuzz_crate_bin"
+path = "test_crates/another_sample_fuzz_crate/src/lib.rs"
+test = false
 
 [[test]]
 name = "runner_test"
 path = "tests/runner_test.rs"
+
+[[test]]
+name = "e2e_cli_test"
+path = "tests/e2e_cli_test.rs"
+
diff --git a/rust/cargo_fuzztest/src/lib.rs b/rust/cargo_fuzztest/src/lib.rs
index 6f61bcf..78b6942 100644
--- a/rust/cargo_fuzztest/src/lib.rs
+++ b/rust/cargo_fuzztest/src/lib.rs
@@ -17,7 +17,9 @@
 
 use anyhow::{Context, Result};
 use clap::Parser;
-pub use fuzztest_options::{ExecutionMode, FuzzFor, FuzzOptions, FuzzTestOptions};
+pub use fuzztest_options::{
+    ExecutionMode, FuzzFor, FuzzOptions, FuzzTestOptions, ReplayCrashOptions,
+};
 use std::env;
 use std::ffi::OsString;
 use std::path::{Path, PathBuf};
@@ -54,7 +56,15 @@
     fn check_centipede_binary_path_is_set(&self) -> Result<()> {
         anyhow::ensure!(
             self.centipede_binary_path.is_some(),
-            "fuzzing mode requires `--centipede-binary-path` to be specified"
+            "`--centipede-binary-path` needs to be specified"
+        );
+        Ok(())
+    }
+
+    fn check_corpus_db_is_set(&self) -> Result<()> {
+        anyhow::ensure!(
+            self.fuzztest_options.corpus_db.is_some(),
+            "`--corpus-db` needs to be specified"
         );
         Ok(())
     }
@@ -72,14 +82,18 @@
                 self.check_centipede_binary_path_is_set()?;
                 mode
             }
+            ExecutionMode::ReplayCrash(_) => {
+                self.check_centipede_binary_path_is_set()?;
+                self.check_corpus_db_is_set()?;
+                mode
+            }
             ExecutionMode::SmokeTest => {
                 if self.test_path.is_some() {
                     self.check_centipede_binary_path_is_set()?;
-                    return Ok(ExecutionMode::Fuzz(FuzzOptions {
-                        fuzz_for: FuzzFor::Indefinitely,
-                        // TODO(the-shank): support parallel jobs
-                        jobs: None,
-                    }));
+                    ExecutionMode::Fuzz(FuzzOptions {
+                        fuzz_for: self.fuzztest_options.fuzz_for.unwrap_or(FuzzFor::Indefinitely),
+                        jobs: self.fuzztest_options.jobs,
+                    })
                 } else {
                     mode
                 }
@@ -241,6 +255,10 @@
                 }
             }
 
+            ExecutionMode::ReplayCrash(replay_options) => {
+                cmd.env("FUZZTEST_REPLAY_ID", replay_options.replay_id);
+            }
+
             ExecutionMode::SmokeTest => {
                 // nothing to be done
             }
@@ -249,6 +267,14 @@
             }
         }
 
+        if let Some(corpus_db) = &self.options.fuzztest_options.corpus_db {
+            cmd.env("FUZZTEST_CORPUS_DB", corpus_db);
+        }
+
+        if let Some(workdir_root) = &self.options.fuzztest_options.workdir_root {
+            cmd.env("FUZZTEST_WORKDIR_ROOT", workdir_root);
+        }
+
         // If `--centipede-binary-path` was passed to `cargo-fuzztest`, forward it to the
         // child test executable via `FUZZTEST_CENTIPEDE_BINARY_PATH` environment variable so the
         // fuzzer runtime can locate and run Centipede during continuous fuzzing.
@@ -256,6 +282,11 @@
             cmd.env("FUZZTEST_CENTIPEDE_BINARY_PATH", centipede_binary_path);
         }
 
+        if self.options.fuzztest_options.print_subprocess_log {
+            cmd.env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true");
+            cmd.arg("--nocapture");
+        }
+
         Ok(cmd)
     }
 
@@ -379,4 +410,88 @@
             ExecutionMode::SmokeTest
         );
     }
+
+    #[gtest]
+    fn test_cli_option_parsing_replay_id_success() {
+        let parsed = CargoFuzzTestOptions::try_parse_from([
+            "cargo-fuzztest",
+            "--replay-id",
+            "crash_12345",
+            "--corpus-db",
+            "/tmp/corpus_db",
+            "--centipede-binary-path",
+            "/custom/centipede",
+        ])
+        .expect("valid replay options should parse successfully");
+
+        assert_eq!(parsed.fuzztest_options.replay_id.as_deref(), Some("crash_12345"));
+        assert_eq!(parsed.fuzztest_options.corpus_db.as_deref(), Some("/tmp/corpus_db"));
+
+        let mode = parsed.execution_mode().expect("valid execution mode");
+        assert_eq!(
+            mode,
+            ExecutionMode::ReplayCrash(ReplayCrashOptions { replay_id: "crash_12345".to_string() })
+        );
+    }
+
+    #[gtest]
+    fn test_execution_mode_replay_id_missing_corpus_db_errors() {
+        let options = CargoFuzzTestOptions {
+            fuzztest_options: FuzzTestOptions {
+                replay_id: Some("crash_12345".to_string()),
+                ..Default::default()
+            },
+            centipede_binary_path: Some("/custom/centipede".to_string()),
+            ..Default::default()
+        };
+        let err = options.execution_mode().expect_err("missing corpus-db should cause error");
+        assert!(err.to_string().contains("`--corpus-db` needs to be specified"));
+    }
+
+    #[gtest]
+    fn test_execution_mode_replay_id_missing_centipede_binary_path_errors() {
+        let options = CargoFuzzTestOptions {
+            fuzztest_options: FuzzTestOptions {
+                replay_id: Some("crash_12345".to_string()),
+                corpus_db: Some("/tmp/corpus_db".to_string()),
+                ..Default::default()
+            },
+            ..Default::default()
+        };
+        let err =
+            options.execution_mode().expect_err("missing centipede-binary-path should cause error");
+        assert!(err.to_string().contains("`--centipede-binary-path` needs to be specified"));
+    }
+
+    #[gtest]
+    fn test_build_run_command_replay_crash() {
+        let options = CargoFuzzTestOptions {
+            fuzztest_options: FuzzTestOptions {
+                replay_id: Some("crash_12345".to_string()),
+                corpus_db: Some("/tmp/corpus_db".to_string()),
+                ..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_REPLAY_ID".to_string(), Some("crash_12345".to_string()))));
+        assert!(
+            envs.contains(&("FUZZTEST_CORPUS_DB".to_string(), Some("/tmp/corpus_db".to_string())))
+        );
+        assert!(envs.contains(&(
+            "FUZZTEST_CENTIPEDE_BINARY_PATH".to_string(),
+            Some("/custom/centipede".to_string())
+        )));
+    }
 }
diff --git a/rust/cargo_fuzztest/test_crates/another_sample_fuzz_crate/Cargo.toml b/rust/cargo_fuzztest/test_crates/another_sample_fuzz_crate/Cargo.toml
new file mode 100644
index 0000000..d863aea
--- /dev/null
+++ b/rust/cargo_fuzztest/test_crates/another_sample_fuzz_crate/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "another_sample_fuzz_crate"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+fuzztest = { path = "../../.." }
+googletest = "0.14.3"
+
+[workspace]
+
diff --git a/rust/cargo_fuzztest/test_crates/another_sample_fuzz_crate/src/lib.rs b/rust/cargo_fuzztest/test_crates/another_sample_fuzz_crate/src/lib.rs
new file mode 100644
index 0000000..0995410
--- /dev/null
+++ b/rust/cargo_fuzztest/test_crates/another_sample_fuzz_crate/src/lib.rs
@@ -0,0 +1,24 @@
+use fuzztest::domains::arbitrary::Arbitrary;
+use fuzztest::fuzztest;
+
+#[fuzztest(data = Arbitrary::<i32>::default())]
+fn sample_fuzztest_target(data: i32) {
+    let _ = data;
+}
+
+#[fuzztest(data = Arbitrary::<i32>::default())]
+fn another_sample_fuzztest_target(data: i32) {
+    let _ = data;
+}
+
+#[fuzztest(data = Arbitrary::<i32>::default())]
+fn jobs_fuzztest_target(_data: i32) {
+    println!("JOBS_TEST_PID: {}", std::process::id());
+}
+
+#[fuzztest(data = Arbitrary::<u8>::default())]
+fn crashing_fuzztest_target(data: u8) {
+    if data == 10 {
+        panic!("Crashing bug found!");
+    }
+}
diff --git a/rust/cargo_fuzztest/tests/common/mod.rs b/rust/cargo_fuzztest/tests/common/mod.rs
new file mode 100644
index 0000000..820ce7b
--- /dev/null
+++ b/rust/cargo_fuzztest/tests/common/mod.rs
@@ -0,0 +1,45 @@
+use cargo_fuzztest::FuzztestRunner;
+use std::env;
+use std::path::PathBuf;
+
+// Helper to locate compiled sample fuzz test executables across different build environments
+// (Cargo tests vs Blaze/Bazel tests).
+pub fn get_sample_test_bin_path(crate_name: &str) -> PathBuf {
+    let test_bin_name = format!("{crate_name}_bin");
+
+    // 1. Cargo test: Check if CARGO_BIN_EXE_<name> is set by Cargo when running integration tests.
+    if let Ok(cargo_bin) = env::var(format!("CARGO_BIN_EXE_{test_bin_name}")) {
+        return PathBuf::from(cargo_bin);
+    }
+
+    // 2. Bazel/Blaze test: Fall back to TEST_SRCDIR and TEST_WORKSPACE.
+    if let Ok(src_dir) = env::var("TEST_SRCDIR") {
+        let test_workspace = env::var("TEST_WORKSPACE").unwrap_or_else(|_| {
+            "_main".to_string()
+        });
+
+        let relative_binary_path =
+        format!("rust/cargo_fuzztest/{test_bin_name}");
+
+        let blaze_path = PathBuf::from(src_dir).join(test_workspace).join(relative_binary_path);
+        assert!(blaze_path.exists());
+        return blaze_path;
+    }
+
+    // 3. Cargo test fallback: Query compiled test executable path via Cargo JSON compiler messages.
+    if env::var("CARGO_MANIFEST_DIR").is_ok() {
+        let cargo_bin = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
+        let output = std::process::Command::new(cargo_bin)
+            .args(["test", "--no-run", "--message-format=json", "--test", &test_bin_name])
+            .output()
+            .expect("cargo test compilation command should execute successfully");
+        assert!(output.status.success());
+
+        let json_stdout = String::from_utf8_lossy(&output.stdout);
+        if let Ok(exe) = FuzztestRunner::parse_compiler_messages(&json_stdout) {
+            return exe;
+        }
+    }
+
+    panic!("Could not locate {test_bin_name} via CARGO_BIN_EXE, TEST_SRCDIR, or Cargo compiler messages");
+}
diff --git a/rust/cargo_fuzztest/tests/e2e_cli_test.rs b/rust/cargo_fuzztest/tests/e2e_cli_test.rs
index 00119a5..221a21d 100644
--- a/rust/cargo_fuzztest/tests/e2e_cli_test.rs
+++ b/rust/cargo_fuzztest/tests/e2e_cli_test.rs
@@ -1,5 +1,6 @@
 use googletest::prelude::*;
 use std::env;
+use std::fs;
 use std::path::{Path, PathBuf};
 use std::process::Command;
 use tempfile::TempDir;
@@ -140,3 +141,135 @@
     }
     expect_that!(pids.len(), eq(4));
 }
+
+#[gtest]
+fn test_cargo_fuzztest_e2e_replay_by_id() {
+    let centipede_bin = env::var("FUZZTEST_CENTIPEDE_BINARY_PATH")
+        .expect("FUZZTEST_CENTIPEDE_BINARY_PATH needs to be set for the test");
+
+    let sample_crate_path = get_sample_crate_path("another_sample_fuzz_crate");
+
+    let temp_target_dir = TempDir::new().expect("Failed to create temporary target directory");
+    fs::create_dir_all(&temp_target_dir).expect("Failed to corpus db directory");
+
+    let temp_db_dir = TempDir::new().expect("Failed to create temporary corpus db directory");
+    fs::create_dir_all(&temp_db_dir).expect("Failed to corpus db directory");
+
+    let workdir_root_dir =
+        TempDir::new().expect("Failed to create temporary workdir_root directory");
+    fs::create_dir_all(&workdir_root_dir).expect("Failed to workdir_root directory");
+
+    let test_target = "__fuzztest_mod__crashing_fuzztest_target::crashing_fuzztest_target";
+    let normalized_test_name = test_target.replace("::", ".");
+
+    // 1. Retrieve the target binary path.
+    let host_triple = cargo_fuzztest::get_host_target_triple()
+        .expect("Failed to get host target triple for compilation");
+    let runner = cargo_fuzztest::FuzztestRunner::new(
+        host_triple,
+        cargo_fuzztest::CargoFuzzTestOptions::default(),
+    );
+    let mut compile_cmd = runner.build_compile_command();
+    compile_cmd.current_dir(&sample_crate_path).env("CARGO_TARGET_DIR", temp_target_dir.path());
+
+    let compile_output = compile_cmd.output().expect("Failed to execute cargo compilation command");
+    assert!(compile_output.status.success());
+    let json_stdout = String::from_utf8(compile_output.stdout)
+        .expect("Cargo compilation stdout must be valid UTF-8");
+    let target_binary_path = cargo_fuzztest::FuzztestRunner::parse_compiler_messages(&json_stdout)
+        .expect("Failed to parse target binary path from cargo JSON output");
+
+    let target_binary_str = target_binary_path.to_str().expect("Valid binary path string");
+    let binary_id = target_binary_str.strip_prefix('/').unwrap_or(target_binary_str);
+
+    // 2. Run Centipede to fuzz the target and populate the corpus database.
+    let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path());
+    cmd.arg(test_target)
+        .arg("--fuzz-for=5s")
+        .env_remove("CENTIPEDE_BINARY_PATH")
+        .arg("--centipede-binary-path")
+        .arg(&centipede_bin)
+        .arg("--corpus-db")
+        .arg(temp_db_dir.path())
+        .arg("--workdir-root")
+        .arg(workdir_root_dir.path());
+
+    let output = cmd.output().expect("Failed to run cargo-fuzztest to fuzz target");
+    assert!(output.status.success());
+
+    // 3. Get list of crash ids
+    let list_temp_dir = TempDir::new().expect("Failed to create temporary list directory");
+    let crash_ids_file = list_temp_dir.path().join("crash_ids.txt");
+
+    let list_args = [
+        format!("--binary={}", target_binary_path.display()),
+        format!("--fuzztest_binary_identifier={}", binary_id),
+        format!("--test_name={}", normalized_test_name),
+        format!("--fuzztest_corpus_database={}", temp_db_dir.path().display()),
+        "--list_crash_ids=1".to_string(),
+        format!("--list_crash_ids_file={}", crash_ids_file.display()),
+    ];
+    let list_args_refs: Vec<&str> = list_args.iter().map(|s| s.as_str()).collect();
+    run_centipede_with_args_expect_termination(&centipede_bin, &list_args_refs);
+
+    let crash_ids_contents =
+        fs::read_to_string(&crash_ids_file).expect("Failed to read crash IDs file");
+    let crash_ids: Vec<&str> =
+        crash_ids_contents.lines().filter(|line| !line.trim().is_empty()).collect();
+
+    expect_true!(!crash_ids.is_empty());
+    let crash_id = crash_ids[0];
+
+    // 4. Run cargo-fuzztest CLI with --replay-id <crash_id> to verify it replays the crash.
+    let mut cmd = setup_cargo_fuzztest_command(&sample_crate_path, temp_target_dir.path());
+    cmd.arg(test_target)
+        .arg("--replay-id")
+        .arg(crash_id)
+        .arg("--corpus-db")
+        .arg(temp_db_dir.path())
+        .arg("--workdir-root")
+        .arg(workdir_root_dir.path())
+        .arg("--centipede-binary-path")
+        .arg(&centipede_bin);
+
+    let output = cmd.output().expect("Failed to run cargo-fuzztest in replay mode");
+
+    let stderr_str = String::from_utf8_lossy(&output.stderr);
+    let stdout_str = String::from_utf8_lossy(&output.stdout);
+    expect_false!(output.status.success());
+    expect_true!(
+        stderr_str.contains("FuzzTest controller reported failure")
+            || stderr_str.contains("Crashing bug found!")
+            || stdout_str.contains("FuzzTest controller reported failure")
+    );
+}
+
+fn run_centipede_with_args_expect_termination(centipede_bin: &str, args: &[&str]) -> String {
+    // Disable interference from Bazel environment variables.
+    let env_diff = [
+        "-TEST_DIAGNOSTICS_OUTPUT_DIR",
+        "-TEST_INFRASTRUCTURE_FAILURE_FILE",
+        "-TEST_LOGSPLITTER_OUTPUT_FILE",
+        "-TEST_PREMATURE_EXIT_FILE",
+        "-TEST_RANDOM_SEED",
+        "-TEST_RUN_NUMBER",
+        "-TEST_SHARD_INDEX",
+        "-TEST_SHARD_STATUS_FILE",
+        "-TEST_TOTAL_SHARDS",
+        "-TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR",
+        "-TEST_UNDECLARED_OUTPUTS_DIR",
+        "-TEST_WARNINGS_OUTPUT_FILE",
+        "-GTEST_OUTPUT",
+        "-XML_OUTPUT_FILE",
+    ];
+    let process = Command::new(centipede_bin)
+        .arg("--populate_binary_info=0")
+        .arg("--fork_server=0")
+        .arg("--persistent_mode=0")
+        .arg(format!("--env_diff_for_binaries={}", env_diff.join(",")))
+        .args(args)
+        .output()
+        .expect("Centipede should have executed");
+
+    String::from_utf8_lossy(&process.stderr).to_string()
+}
diff --git a/rust/cargo_fuzztest/tests/runner_test.rs b/rust/cargo_fuzztest/tests/runner_test.rs
index ee2dce5..b45852c 100644
--- a/rust/cargo_fuzztest/tests/runner_test.rs
+++ b/rust/cargo_fuzztest/tests/runner_test.rs
@@ -1,12 +1,13 @@
+mod common;
+
 use cargo_fuzztest::{CargoFuzzTestOptions, FuzztestRunner};
+use common::get_sample_test_bin_path;
 use fuzztest_options::{FuzzFor, FuzzTestOptions};
 use googletest::prelude::*;
-use std::env;
-use std::path::PathBuf;
 
 #[gtest]
 fn test_runner_execution() {
-    let binary_path = get_sample_fuzz_test_bin_path();
+    let binary_path = get_sample_test_bin_path("sample_fuzz_crate");
 
     assert!(binary_path.exists(), "Sample fuzz binary does not exist at {}", binary_path.display());
 
@@ -24,7 +25,7 @@
 
 #[gtest]
 fn test_runner_build_run_command_with_target() {
-    let binary_path = get_sample_fuzz_test_bin_path();
+    let binary_path = get_sample_test_bin_path("sample_fuzz_crate");
     let options = CargoFuzzTestOptions {
         test_path: Some(
             "__fuzztest_mod__sample_fuzztest_target::sample_fuzztest_target".to_string(),
@@ -43,7 +44,7 @@
 
 #[gtest]
 fn test_runner_build_run_command_with_duration() {
-    let binary_path = get_sample_fuzz_test_bin_path();
+    let binary_path = get_sample_test_bin_path("sample_fuzz_crate");
     let fuzztest_options = FuzzTestOptions {
         fuzz_for: Some(FuzzFor::Duration("5s".parse().unwrap())),
         ..Default::default()
@@ -65,7 +66,7 @@
 
 #[gtest]
 fn test_runner_build_run_command_with_indefinitely() {
-    let binary_path = get_sample_fuzz_test_bin_path();
+    let binary_path = get_sample_test_bin_path("sample_fuzz_crate");
     let fuzztest_options =
         FuzzTestOptions { fuzz_for: Some(FuzzFor::Indefinitely), ..Default::default() };
     let options = CargoFuzzTestOptions {
@@ -85,7 +86,7 @@
 
 #[gtest]
 fn test_runner_build_run_command_with_centipede_binary_path() {
-    let binary_path = get_sample_fuzz_test_bin_path();
+    let binary_path = get_sample_test_bin_path("sample_fuzz_crate");
     let options = CargoFuzzTestOptions {
         centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
         ..Default::default()
@@ -105,7 +106,7 @@
 
 #[gtest]
 fn test_runner_build_run_command_with_jobs() {
-    let binary_path = get_sample_fuzz_test_bin_path();
+    let binary_path = get_sample_test_bin_path("sample_fuzz_crate");
     let fuzztest_options = FuzzTestOptions {
         jobs: Some(4),
         fuzz_for: Some(FuzzFor::Duration("10s".parse().expect("static valid duration string"))),
@@ -129,7 +130,7 @@
 
 #[gtest]
 fn test_runner_build_run_command_with_jobs_only() {
-    let binary_path = get_sample_fuzz_test_bin_path();
+    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);
@@ -144,17 +145,70 @@
 }
 
 #[gtest]
-fn test_execution_mode_without_centipede_binary_path_errors() {
+fn test_runner_build_run_command_with_replay_id() {
+    let binary_path = get_sample_test_bin_path("sample_fuzz_crate");
+    let fuzztest_options = FuzzTestOptions {
+        replay_id: Some("crash_12345".to_string()),
+        corpus_db: Some("/custom/path/to/corpus_db".to_string()),
+        ..Default::default()
+    };
+    let options = CargoFuzzTestOptions {
+        fuzztest_options,
+        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 cmd = runner.build_run_command(&binary_path).expect("valid 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();
+    expect_true!(
+        envs.contains(&("FUZZTEST_REPLAY_ID".to_string(), Some("crash_12345".to_string())))
+    );
+    expect_true!(envs.contains(&(
+        "FUZZTEST_CORPUS_DB".to_string(),
+        Some("/custom/path/to/corpus_db".to_string())
+    )));
+    expect_true!(envs.contains(&(
+        "FUZZTEST_CENTIPEDE_BINARY_PATH".to_string(),
+        Some("/custom/path/to/centipede".to_string())
+    )));
+}
+
+#[gtest]
+fn test_execution_mode_replay_id_missing_corpus_db_errors() {
     let fuzztest_options =
-        FuzzTestOptions { fuzz_for: Some(FuzzFor::Indefinitely), ..Default::default() };
+        FuzzTestOptions { replay_id: Some("crash_12345".to_string()), ..Default::default() };
+    let options = CargoFuzzTestOptions {
+        fuzztest_options,
+        centipede_binary_path: Some("/custom/path/to/centipede".to_string()),
+        ..Default::default()
+    };
+    let result = options.execution_mode();
+    expect_true!(result.is_err());
+    let err_msg = result.unwrap_err().to_string();
+    expect_true!(err_msg.contains("`--corpus-db` needs to be specified"));
+}
+
+#[gtest]
+fn test_execution_mode_replay_id_missing_centipede_binary_path_errors() {
+    let fuzztest_options = FuzzTestOptions {
+        replay_id: Some("crash_12345".to_string()),
+        corpus_db: Some("/custom/path/to/corpus_db".to_string()),
+        ..Default::default()
+    };
     let options = CargoFuzzTestOptions { fuzztest_options, ..Default::default() };
     let result = options.execution_mode();
     expect_true!(result.is_err());
+    let err_msg = result.unwrap_err().to_string();
+    expect_true!(err_msg.contains("`--centipede-binary-path` needs to be specified"));
 }
 
 #[gtest]
 fn test_runner_list_command() {
-    let binary_path = get_sample_fuzz_test_bin_path();
+    let binary_path = get_sample_test_bin_path("sample_fuzz_crate");
 
     assert!(binary_path.exists(), "Sample fuzz binary does not exist at {}", binary_path.display());
 
@@ -167,43 +221,3 @@
     let args: Vec<String> = cmd.get_args().map(|a| a.to_string_lossy().to_string()).collect();
     expect_eq!(args, &["__fuzztest_mod__", "--list"]);
 }
-
-fn get_sample_fuzz_test_bin_path() -> PathBuf {
-    // 1. Cargo test: Check if CARGO_BIN_EXE_<name> is set
-    if let Ok(cargo_bin) = env::var("CARGO_BIN_EXE_sample_fuzz_test_bin") {
-        return PathBuf::from(cargo_bin);
-    }
-
-    // 2. Bazel/Blaze test: Fall back to TEST_SRCDIR and TEST_WORKSPACE
-    if let Ok(src_dir) = env::var("TEST_SRCDIR") {
-        let test_workspace = env::var("TEST_WORKSPACE").unwrap_or_else(|_| {
-            "_main".to_string()
-        });
-
-        const RELATIVE_BINARY_PATH: &str =
-        "rust/cargo_fuzztest/sample_fuzz_test_bin";
-
-        let blaze_path = PathBuf::from(src_dir).join(test_workspace).join(RELATIVE_BINARY_PATH);
-        if blaze_path.exists() {
-            return blaze_path;
-        }
-    }
-
-    // 2. Cargo test fallback: Query compiled test executable path via Cargo JSON compiler messages
-    if env::var("CARGO_MANIFEST_DIR").is_ok() {
-        let cargo_bin = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
-        let output = std::process::Command::new(cargo_bin)
-            .args(["test", "--no-run", "--message-format=json", "--test", "sample_fuzz_test_bin"])
-            .output()
-            .expect("Failed to execute `cargo test --no-run --message-format=json` to locate test binary");
-
-        if output.status.success() {
-            let json_stdout = String::from_utf8_lossy(&output.stdout);
-            if let Ok(exe) = FuzztestRunner::parse_compiler_messages(&json_stdout) {
-                return exe;
-            }
-        }
-    }
-
-    panic!("Could not locate sample_fuzz_test_bin via CARGO_BIN_EXE, TEST_SRCDIR, or Cargo compiler messages");
-}