@@ -15,7 +15,8 @@ use std::hash::{Hash, Hasher};
1515use std::io::{self, BufRead, BufReader, IsTerminal, Write};
1616use std::path::{Path, PathBuf};
1717use std::process::{Command, Stdio};
18- use std::sync::{Mutex, OnceLock};
18+ use std::sync::atomic::{AtomicBool, Ordering};
19+ use std::sync::{Arc, Mutex, OnceLock};
1920use std::thread;
2021use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
2122
@@ -3804,6 +3805,12 @@ struct CodexSessionReportBaseline {
38043805 tree_ids: BTreeSet<String>,
38053806}
38063807
3808+ #[derive(Clone, Debug)]
3809+ struct CodexSessionReportHandle {
3810+ cancel: Arc<AtomicBool>,
3811+ report_path: PathBuf,
3812+ }
3813+
38073814fn codex_session_report_path_from_env() -> Option<PathBuf> {
38083815 env::var(FLOW_CODEX_SESSION_REPORT_PATH_ENV)
38093816 .ok()
@@ -3901,16 +3908,25 @@ fn find_new_codex_session_report_id(
39013908 .map(|row| row.id)
39023909}
39033910
3904- fn start_new_codex_session_reporter(report_path: PathBuf, target_path: PathBuf) {
3911+ fn start_new_codex_session_reporter(
3912+ report_path: PathBuf,
3913+ target_path: PathBuf,
3914+ ) -> CodexSessionReportHandle {
39053915 let baseline = capture_codex_session_report_baseline(&target_path);
3916+ let cancel = Arc::new(AtomicBool::new(false));
3917+ let cancel_worker = Arc::clone(&cancel);
3918+ let worker_report_path = report_path.clone();
39063919 thread::spawn(move || {
39073920 let started_at = Instant::now();
39083921 while started_at.elapsed() < CODEX_SESSION_REPORT_POLL_TIMEOUT {
3922+ if cancel_worker.load(Ordering::Relaxed) {
3923+ return;
3924+ }
39093925 if let Some(session_id) = find_new_codex_session_report_id(&target_path, &baseline) {
3910- if let Err(err) = write_codex_session_report(&report_path , &session_id) {
3926+ if let Err(err) = write_codex_session_report(&worker_report_path , &session_id) {
39113927 debug!(
39123928 error = %err,
3913- path = %report_path .display(),
3929+ path = %worker_report_path .display(),
39143930 "failed to write new Codex session report"
39153931 );
39163932 }
@@ -3920,13 +3936,18 @@ fn start_new_codex_session_reporter(report_path: PathBuf, target_path: PathBuf)
39203936 thread::sleep(CODEX_SESSION_REPORT_POLL_INTERVAL);
39213937 }
39223938
3923- clear_pending_codex_session_report(&report_path );
3939+ clear_pending_codex_session_report(&worker_report_path );
39243940 debug!(
3925- path = %report_path .display(),
3941+ path = %worker_report_path .display(),
39263942 target_path = %target_path.display(),
39273943 "timed out while waiting for a new Codex session id to appear"
39283944 );
39293945 });
3946+
3947+ CodexSessionReportHandle {
3948+ cancel,
3949+ report_path,
3950+ }
39303951}
39313952
39323953fn launch_session_for_target(
@@ -4531,15 +4552,34 @@ fn launch_codex_resume_picker() -> Result<bool> {
45314552 Ok(status.success())
45324553}
45334554
4534- fn launch_codex_continue_last_for_target(target_path: Option<&Path>) -> Result<bool> {
4555+ fn launch_codex_continue_last_for_target(
4556+ target_path: Option<&Path>,
4557+ require_existing_session: bool,
4558+ ) -> Result<bool> {
45354559 let workdir = target_path
45364560 .map(Path::to_path_buf)
45374561 .unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
4538- if let Some(session_id) = read_recent_codex_threads(&workdir, true, 1, None)?
4539- .first()
4540- .map(|row| row.id.clone())
4562+ let report_path = codex_session_report_path_from_env();
4563+ let recent_session_id = if require_existing_session || report_path.is_some() {
4564+ read_recent_codex_threads(&workdir, true, 1, None)?
4565+ .first()
4566+ .map(|row| row.id.clone())
4567+ } else {
4568+ None
4569+ };
4570+ if require_existing_session && recent_session_id.is_none() {
4571+ return Ok(false);
4572+ }
4573+ if let (Some(report_path), Some(session_id)) =
4574+ (report_path.as_deref(), recent_session_id.as_deref())
45414575 {
4542- maybe_write_codex_session_report(&session_id);
4576+ if let Err(err) = write_codex_session_report(report_path, session_id) {
4577+ debug!(
4578+ error = %err,
4579+ path = %report_path.display(),
4580+ "failed to write Codex session report"
4581+ );
4582+ }
45434583 }
45444584 let trace = new_codex_session_trace("continue_last_session");
45454585 let mut command = Command::new(configured_codex_bin_for_workdir(&workdir));
@@ -4800,7 +4840,7 @@ fn continue_session(
48004840
48014841 let launched = match provider {
48024842 Provider::Claude => launch_claude_continue()?,
4803- Provider::Codex => launch_codex_continue_last_for_target(None)?,
4843+ Provider::Codex => launch_codex_continue_last_for_target(None, false )?,
48044844 Provider::Cursor => false,
48054845 Provider::All => false,
48064846 };
@@ -4815,7 +4855,7 @@ fn continue_session(
48154855/// Quick start: continue last session or create new one with dangerous flags.
48164856pub fn quick_start_session(provider: Provider) -> Result<()> {
48174857 if provider == Provider::Codex {
4818- let launched = launch_codex_continue_last_for_target(None)?;
4858+ let launched = launch_codex_continue_last_for_target(None, false )?;
48194859 if !launched {
48204860 new_session(provider)?;
48214861 }
@@ -4891,24 +4931,29 @@ fn new_session_for_target(
48914931 if let Some(prompt) = prompt.map(str::trim).filter(|value| !value.is_empty()) {
48924932 command.arg(prompt);
48934933 }
4894- let report_path = codex_session_report_path_from_env();
4895- if let Some(report_path) = report_path.clone() {
4934+ let report_handle = if let Some(report_path) = codex_session_report_path_from_env() {
48964935 if let Err(err) =
48974936 write_codex_session_report(&report_path, CODEX_SESSION_REPORT_PENDING)
48984937 {
48994938 debug!(
49004939 error = %err,
49014940 path = %report_path.display(),
4902- "failed to clear stale Codex session report before new launch"
4941+ "failed to write pending Codex session report before new launch"
49034942 );
49044943 }
4905- start_new_codex_session_reporter(report_path, workdir.clone());
4906- }
4944+ Some(start_new_codex_session_reporter(
4945+ report_path,
4946+ workdir.clone(),
4947+ ))
4948+ } else {
4949+ None
4950+ };
49074951 let status = command.status().with_context(|| "failed to launch codex")?;
4908- if !status.success()
4909- && let Some(report_path) = report_path.as_deref()
4910- {
4911- clear_pending_codex_session_report(report_path);
4952+ if !status.success() {
4953+ if let Some(report_handle) = report_handle {
4954+ report_handle.cancel.store(true, Ordering::Relaxed);
4955+ clear_pending_codex_session_report(&report_handle.report_path);
4956+ }
49124957 }
49134958 if status.success() && direct_log {
49144959 record_direct_codex_launch_event(
@@ -7034,7 +7079,7 @@ fn connect_codex_session(
70347079 let query_text = query.join(" ").trim().to_string();
70357080 if should_fast_path_codex_connect(&query_text, exact_cwd, json_output) {
70367081 ensure_provider_tty(Provider::Codex, "connect")?;
7037- if launch_codex_continue_last_for_target(Some(&target_path))? {
7082+ if launch_codex_continue_last_for_target(Some(&target_path), true )? {
70387083 record_codex_connect_activity(
70397084 "resume latest recent session",
70407085 "latest",
@@ -15428,6 +15473,24 @@ mod tests {
1542815473 assert!(!should_fast_path_codex_connect("", true, true));
1542915474 }
1543015475
15476+ #[test]
15477+ fn clear_pending_codex_session_report_only_removes_pending_marker() {
15478+ let root = tempdir().expect("tempdir");
15479+ let report_path = root.path().join("codex-session-report.txt");
15480+
15481+ fs::write(&report_path, format!("{CODEX_SESSION_REPORT_PENDING}\n"))
15482+ .expect("write pending marker");
15483+ clear_pending_codex_session_report(&report_path);
15484+ assert!(!report_path.exists());
15485+
15486+ fs::write(&report_path, "session-123\n").expect("write session id");
15487+ clear_pending_codex_session_report(&report_path);
15488+ assert_eq!(
15489+ fs::read_to_string(&report_path).expect("read session id"),
15490+ "session-123\n"
15491+ );
15492+ }
15493+
1543115494 #[test]
1543215495 fn select_codex_state_db_path_prefers_highest_version() {
1543315496 let root = tempdir().expect("tempdir");
0 commit comments