mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[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:
committed by
GitHub
Unverified
parent
1feaa7d85b
commit
394242e95b
+181
-10
@@ -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<()>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user