Skip to content

Commit 0a1a9a6

Browse files
committed
ai: harden codex session reporting
1 parent 83a5f89 commit 0a1a9a6

1 file changed

Lines changed: 86 additions & 23 deletions

File tree

src/ai.rs

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ use std::hash::{Hash, Hasher};
1515
use std::io::{self, BufRead, BufReader, IsTerminal, Write};
1616
use std::path::{Path, PathBuf};
1717
use std::process::{Command, Stdio};
18-
use std::sync::{Mutex, OnceLock};
18+
use std::sync::atomic::{AtomicBool, Ordering};
19+
use std::sync::{Arc, Mutex, OnceLock};
1920
use std::thread;
2021
use 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+
38073814
fn 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

39323953
fn 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.
48164856
pub 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

Comments
 (0)