From 32d2f75865cca04ac6ee00696ae3911eb7b4ef8f Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Mon, 4 May 2026 13:33:00 -0700 Subject: [PATCH] Align exec resume cwd filtering --- codex-rs/exec/src/lib.rs | 53 ++++++++--------------------- codex-rs/exec/tests/suite/resume.rs | 11 +++--- 2 files changed, 19 insertions(+), 45 deletions(-) diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index d61346f1d0..5eb1c31fe3 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -35,6 +35,7 @@ use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ServerRequest; use codex_app_server_protocol::Thread as AppServerThread; use codex_app_server_protocol::ThreadItem as AppServerThreadItem; +use codex_app_server_protocol::ThreadListCwdFilter; use codex_app_server_protocol::ThreadListParams; use codex_app_server_protocol::ThreadListResponse; use codex_app_server_protocol::ThreadReadParams; @@ -85,8 +86,6 @@ use codex_protocol::models::PermissionProfile; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::ReviewRequest; use codex_protocol::protocol::ReviewTarget; -use codex_protocol::protocol::RolloutItem; -use codex_protocol::protocol::RolloutLine; use codex_protocol::protocol::SessionConfiguredEvent; use codex_protocol::protocol::SessionSource; use codex_protocol::user_input::UserInput; @@ -1293,36 +1292,17 @@ fn all_thread_source_kinds() -> Vec { ] } -async fn latest_thread_cwd(thread: &AppServerThread) -> PathBuf { - if let Some(path) = thread.path.as_deref() - && let Some(cwd) = parse_latest_turn_context_cwd(path).await - { - return cwd; - } - thread.cwd.to_path_buf() -} - -async fn parse_latest_turn_context_cwd(path: &Path) -> Option { - let text = tokio::fs::read_to_string(path).await.ok()?; - for line in text.lines().rev() { - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - let Ok(rollout_line) = serde_json::from_str::(trimmed) else { - continue; - }; - if let RolloutItem::TurnContext(item) = rollout_line.item { - return Some(item.cwd); - } - } - None -} - fn cwds_match(current_cwd: &Path, session_cwd: &Path) -> bool { path_utils::paths_match_after_normalization(current_cwd, session_cwd) } +fn resume_thread_list_cwd_filter( + config: &Config, + args: &crate::cli::ResumeArgs, +) -> Option { + (!args.all).then(|| ThreadListCwdFilter::One(config.cwd.to_string_lossy().into_owned())) +} + async fn resolve_resume_thread_id( client: &InProcessAppServerClient, config: &Config, @@ -1330,6 +1310,7 @@ async fn resolve_resume_thread_id( args: &crate::cli::ResumeArgs, ) -> anyhow::Result> { let model_providers = resume_lookup_model_providers(config, args); + let cwd_filter = resume_thread_list_cwd_filter(config, args); if args.last { let mut cursor = None; @@ -1346,7 +1327,7 @@ async fn resolve_resume_thread_id( model_providers: model_providers.clone(), source_kinds: Some(all_thread_source_kinds()), archived: Some(false), - cwd: None, + cwd: cwd_filter.clone(), use_state_db_only: false, search_term: None, }, @@ -1355,11 +1336,8 @@ async fn resolve_resume_thread_id( ) .await .map_err(anyhow::Error::msg)?; - for thread in response.data { - let latest_cwd = latest_thread_cwd(&thread).await; - if args.all || cwds_match(config.cwd.as_path(), latest_cwd.as_path()) { - return Ok(Some(thread.id)); - } + if let Some(thread) = response.data.into_iter().next() { + return Ok(Some(thread.id)); } let Some(next_cursor) = response.next_cursor else { return Ok(None); @@ -1411,7 +1389,7 @@ async fn resolve_resume_thread_id( model_providers: model_providers.clone(), source_kinds: Some(all_thread_source_kinds()), archived: Some(false), - cwd: None, + cwd: cwd_filter.clone(), use_state_db_only: false, search_term: Some(session_id.to_string()), }, @@ -1424,10 +1402,7 @@ async fn resolve_resume_thread_id( if thread.name.as_deref() != Some(session_id) { continue; } - let latest_cwd = latest_thread_cwd(&thread).await; - if args.all || cwds_match(config.cwd.as_path(), latest_cwd.as_path()) { - return Ok(Some(thread.id)); - } + return Ok(Some(thread.id)); } let Some(next_cursor) = response.next_cursor else { return Ok(None); diff --git a/codex-rs/exec/tests/suite/resume.rs b/codex-rs/exec/tests/suite/resume.rs index cfaa7aa81a..ba57ada6c0 100644 --- a/codex-rs/exec/tests/suite/resume.rs +++ b/codex-rs/exec/tests/suite/resume.rs @@ -247,7 +247,7 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> { .success(); let sessions_dir = test.home_path().join("sessions"); - find_session_file_containing_marker(&sessions_dir, &marker_a) + let path_a = find_session_file_containing_marker(&sessions_dir, &marker_a) .expect("no session file found for marker_a"); let path_b = find_session_file_containing_marker(&sessions_dir, &marker_b) .expect("no session file found for marker_b"); @@ -312,12 +312,11 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> { let resumed_path_cwd = find_session_file_containing_marker(&sessions_dir, &marker_a2) .expect("no resumed session file containing marker_a2"); - // The `--all` resume above appends a new turn to `path_b` while running from `dir_a`, so the - // session's latest cwd now matches `dir_a`. A subsequent `resume --last` should therefore pick - // the newest matching session (`path_b`). + // Cwd narrowing follows the backend `thread/list` filter, which matches the stored session + // cwd rather than later turn cwd overrides. assert_eq!( - resumed_path_cwd, path_b, - "resume --last should prefer sessions whose latest turn context matches the current cwd" + resumed_path_cwd, path_a, + "resume --last should prefer sessions whose stored cwd matches the current cwd" ); Ok(())