[codex] Fix fork --last cwd filtering (#21089)

Fixes #20945.

This keeps `codex fork --last` aligned with the neighboring
latest-session lookup flows. The local fork path now uses the same
cwd-scope helper as `resume --last`, which is also a small code cleanup
around how this selection logic is shared.

Credit to @chanwooyang1 for the report and for pointing out the narrow
fix direction.

What changed:
- Route `fork --last` through the shared latest-session cwd filter.
- Preserve `--all` as the explicit opt-in for global latest-session
selection.
- Keep remote cwd override behavior unchanged.
- Add focused coverage for local default, `--all`, and remote override
filter semantics.

Validation:
- Ran `just fmt`.
- Ran `git diff --check`.
- Reviewed the `fork --last`, `resume --last`, and fork picker selection
paths against the issue report.
This commit is contained in:
canvrno-oai
2026-05-05 10:33:40 -07:00
committed by GitHub
Unverified
parent 1feaa7d85b
commit 394242e95b
+181 -10
View File
@@ -1278,16 +1278,12 @@ async fn run_ratatui_app(
}
}
} else if cli.fork_last {
let filter_cwd = if remote_mode {
latest_session_cwd_filter(
remote_mode,
remote_cwd_override.as_deref(),
&config,
cli.fork_show_all,
)
} else {
None
};
let filter_cwd = latest_session_cwd_filter(
remote_mode,
remote_cwd_override.as_deref(),
&config,
cli.fork_show_all,
);
let Some(app_server) = app_server.as_mut() else {
unreachable!("app server should be initialized for --fork --last");
};
@@ -1885,6 +1881,181 @@ mod tests {
Ok(())
}
#[tokio::test]
async fn latest_session_cwd_filter_respects_scope_options() -> std::io::Result<()> {
let temp_dir = TempDir::new()?;
let config = build_config(&temp_dir).await?;
let remote_cwd = Path::new("repo/on/server");
let local_filter = latest_session_cwd_filter(
/*remote_mode*/ false, /*remote_cwd_override*/ None, &config,
/*show_all*/ false,
);
let show_all_filter = latest_session_cwd_filter(
/*remote_mode*/ false, /*remote_cwd_override*/ None, &config,
/*show_all*/ true,
);
let remote_filter = latest_session_cwd_filter(
/*remote_mode*/ true,
Some(remote_cwd),
&config,
/*show_all*/ false,
);
assert_eq!(local_filter, Some(config.cwd.as_path()));
assert_eq!(show_all_filter, None);
assert_eq!(remote_filter, Some(remote_cwd));
Ok(())
}
#[tokio::test]
async fn fork_last_filters_latest_session_by_cwd_unless_show_all() -> color_eyre::Result<()> {
fn write_session_rollout(
codex_home: &Path,
filename_ts: &str,
meta_rfc3339: &str,
preview: &str,
model_provider: &str,
cwd: &Path,
) -> color_eyre::Result<ThreadId> {
let uuid = Uuid::new_v4();
let uuid_str = uuid.to_string();
let thread_id = ThreadId::from_string(&uuid_str)?;
let year = &filename_ts[0..4];
let month = &filename_ts[5..7];
let day = &filename_ts[8..10];
let rollout_path = codex_home
.join("sessions")
.join(year)
.join(month)
.join(day)
.join(format!("rollout-{filename_ts}-{uuid_str}.jsonl"));
let parent = rollout_path.parent().ok_or_else(|| {
color_eyre::eyre::eyre!("rollout path is missing a parent directory")
})?;
std::fs::create_dir_all(parent)?;
let session_meta = codex_protocol::protocol::SessionMeta {
id: thread_id,
timestamp: meta_rfc3339.to_string(),
cwd: cwd.to_path_buf(),
originator: "codex".to_string(),
cli_version: "0.0.0".to_string(),
source: codex_protocol::protocol::SessionSource::Cli,
model_provider: Some(model_provider.to_string()),
..Default::default()
};
let session_meta = serde_json::to_value(codex_protocol::protocol::SessionMetaLine {
meta: session_meta,
git: None,
})?;
let lines = [
serde_json::json!({
"timestamp": meta_rfc3339,
"type": "session_meta",
"payload": session_meta,
})
.to_string(),
serde_json::json!({
"timestamp": meta_rfc3339,
"type": "response_item",
"payload": {
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": preview}],
},
})
.to_string(),
serde_json::json!({
"timestamp": meta_rfc3339,
"type": "event_msg",
"payload": {
"type": "user_message",
"message": preview,
"kind": "plain",
},
})
.to_string(),
];
std::fs::write(&rollout_path, lines.join("\n") + "\n")?;
let updated_at =
chrono::DateTime::parse_from_rfc3339(meta_rfc3339)?.with_timezone(&chrono::Utc);
let times = std::fs::FileTimes::new().set_modified(updated_at.into());
OpenOptions::new()
.append(true)
.open(rollout_path)?
.set_times(times)?;
Ok(thread_id)
}
let temp_dir = TempDir::new()?;
let project_cwd = temp_dir.path().join("project");
let other_cwd = temp_dir.path().join("other-project");
std::fs::create_dir_all(&project_cwd)?;
std::fs::create_dir_all(&other_cwd)?;
let config = ConfigBuilder::default()
.codex_home(temp_dir.path().to_path_buf())
.harness_overrides(ConfigOverrides {
cwd: Some(project_cwd.clone()),
..Default::default()
})
.build()
.await?;
let model_provider = config.model_provider_id.as_str();
let project_thread_id = write_session_rollout(
temp_dir.path(),
"2025-01-02T10-00-00",
"2025-01-02T10:00:00Z",
"older project session",
model_provider,
&project_cwd,
)?;
let other_thread_id = write_session_rollout(
temp_dir.path(),
"2025-01-02T12-00-00",
"2025-01-02T12:00:00Z",
"newer other project session",
model_provider,
&other_cwd,
)?;
let mut app_server =
AppServerSession::new(codex_app_server_client::AppServerClient::InProcess(
start_test_embedded_app_server(config.clone()).await?,
));
let filter_cwd = latest_session_cwd_filter(
/*remote_mode*/ false, /*remote_cwd_override*/ None, &config,
/*show_all*/ false,
);
let scoped_target = lookup_latest_session_target_with_app_server(
&mut app_server,
&config,
filter_cwd,
/*include_non_interactive*/ false,
)
.await?
.expect("expected project-scoped fork --last target");
let show_all_filter_cwd = latest_session_cwd_filter(
/*remote_mode*/ false, /*remote_cwd_override*/ None, &config,
/*show_all*/ true,
);
let show_all_target = lookup_latest_session_target_with_app_server(
&mut app_server,
&config,
show_all_filter_cwd,
/*include_non_interactive*/ false,
)
.await?
.expect("expected global fork --last target");
app_server.shutdown().await?;
assert_eq!(scoped_target.thread_id, project_thread_id);
assert_eq!(show_all_target.thread_id, other_thread_id);
Ok(())
}
#[tokio::test]
async fn config_cwd_for_app_server_target_omits_cwd_for_remote_sessions() -> std::io::Result<()>
{