Changed codex resume --last to honor the current cwd (#9245)

This PR changes `codex resume --last` to work consistently with `codex
resume`. Namely, it filters based on the cwd when selecting the last
session. It also supports the `--all` modifier as an override.

This addresses #8700
This commit is contained in:
Eric Traut
2026-01-15 09:05:08 -08:00
committed by GitHub
parent 3fc487e0e0
commit ae96a15312
3 changed files with 81 additions and 18 deletions

View File

@@ -28,6 +28,7 @@ use super::policy::is_persisted_response_item;
use crate::config::Config;
use crate::default_client::originator;
use crate::git_info::collect_git_info;
use crate::path_utils;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::ResumedHistory;
use codex_protocol::protocol::RolloutItem;
@@ -113,6 +114,35 @@ impl RolloutRecorder {
.await
}
/// Find the newest recorded thread path, optionally filtering to a matching cwd.
pub async fn find_latest_thread_path(
codex_home: &Path,
allowed_sources: &[SessionSource],
model_providers: Option<&[String]>,
default_provider: &str,
filter_cwd: Option<&Path>,
) -> std::io::Result<Option<PathBuf>> {
let mut cursor: Option<Cursor> = None;
loop {
let page = Self::list_threads(
codex_home,
25,
cursor.as_ref(),
allowed_sources,
model_providers,
default_provider,
)
.await?;
if let Some(path) = select_resume_path(&page, filter_cwd) {
return Ok(Some(path));
}
cursor = page.next_cursor;
if cursor.is_none() {
return Ok(None);
}
}
}
/// Attempt to create a new [`RolloutRecorder`]. If the sessions directory
/// cannot be created or the rollout file cannot be opened we return the
/// error so the caller can decide whether to disable persistence.
@@ -431,3 +461,36 @@ impl JsonlWriter {
Ok(())
}
}
fn select_resume_path(page: &ThreadsPage, filter_cwd: Option<&Path>) -> Option<PathBuf> {
match filter_cwd {
Some(cwd) => page.items.iter().find_map(|item| {
if session_cwd_matches(&item.head, cwd) {
Some(item.path.clone())
} else {
None
}
}),
None => page.items.first().map(|item| item.path.clone()),
}
}
fn session_cwd_matches(head: &[serde_json::Value], cwd: &Path) -> bool {
let Some(session_cwd) = extract_session_cwd(head) else {
return false;
};
if let (Ok(ca), Ok(cb)) = (
path_utils::normalize_for_path_comparison(&session_cwd),
path_utils::normalize_for_path_comparison(cwd),
) {
return ca == cb;
}
session_cwd == cwd
}
fn extract_session_cwd(head: &[serde_json::Value]) -> Option<PathBuf> {
head.iter().find_map(|value| {
let meta_line = serde_json::from_value::<SessionMetaLine>(value.clone()).ok()?;
Some(meta_line.meta.cwd)
})
}

View File

@@ -522,22 +522,22 @@ async fn run_ratatui_app(
}
} else if cli.resume_last {
let provider_filter = vec![config.model_provider_id.clone()];
match RolloutRecorder::list_threads(
let filter_cwd = if cli.resume_show_all {
None
} else {
Some(config.cwd.as_path())
};
match RolloutRecorder::find_latest_thread_path(
&config.codex_home,
1,
None,
INTERACTIVE_SESSION_SOURCES,
Some(provider_filter.as_slice()),
&config.model_provider_id,
filter_cwd,
)
.await
{
Ok(page) => page
.items
.first()
.map(|it| resume_picker::SessionSelection::Resume(it.path.clone()))
.unwrap_or(resume_picker::SessionSelection::StartFresh),
Err(_) => resume_picker::SessionSelection::StartFresh,
Ok(Some(path)) => resume_picker::SessionSelection::Resume(path),
_ => resume_picker::SessionSelection::StartFresh,
}
} else if cli.resume_picker {
match resume_picker::run_resume_picker(

View File

@@ -545,22 +545,22 @@ async fn run_ratatui_app(
}
} else if cli.resume_last {
let provider_filter = vec![config.model_provider_id.clone()];
match RolloutRecorder::list_threads(
let filter_cwd = if cli.resume_show_all {
None
} else {
Some(config.cwd.as_path())
};
match RolloutRecorder::find_latest_thread_path(
&config.codex_home,
1,
None,
INTERACTIVE_SESSION_SOURCES,
Some(provider_filter.as_slice()),
&config.model_provider_id,
filter_cwd,
)
.await
{
Ok(page) => page
.items
.first()
.map(|it| resume_picker::SessionSelection::Resume(it.path.clone()))
.unwrap_or(resume_picker::SessionSelection::StartFresh),
Err(_) => resume_picker::SessionSelection::StartFresh,
Ok(Some(path)) => resume_picker::SessionSelection::Resume(path),
_ => resume_picker::SessionSelection::StartFresh,
}
} else if cli.resume_picker {
match resume_picker::run_resume_picker(