mirror of
https://github.com/openai/codex.git
synced 2026-10-26 18:38:47 +00:00
Preserve runtime workspace roots across thread resume (#43848)
## Why Resuming a thread should retain its selected workspace folders, including additional roots and explicit empty selections. Resume overrides also need to survive a subsequent resume when no turn has run. ## What changed - Persist `runtime_workspace_roots` in startup metadata and thread settings snapshots, separately from explicit environment selections and permission-profile roots. - Restore roots from the latest snapshot owned by the resumed thread, falling back to owned startup metadata only when no snapshot exists. Honor explicit `runtimeWorkspaceRoots` overrides, retarget the old `cwd` root when `cwd` changes, deduplicate roots, and validate restored paths for the current host. - Checkpoint effective settings on resume and restored settings after revert. Reload resume configuration if saved workspace roots change during loading. - Normalize Windows rollout path spellings when matching thread search results, preserving selection of the correct rollout after revert, including compressed rollouts. ## Testing Add regression coverage for workspace restoration, empty and explicit overrides, foreign paths, compaction and revert, resume checkpoints without recency changes, concurrent settings persistence, and rollout search path matching. GitOrigin-RevId: d98d9d34dd63934d441120916c61c12b69e7f062
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//! Path normalization, symlink resolution, and atomic writes shared across Codex crates.
|
||||
//! Path normalization, replacement, symlink resolution, and atomic writes.
|
||||
|
||||
pub(crate) mod env;
|
||||
pub use env::is_wsl;
|
||||
@@ -8,6 +8,7 @@ pub use system_commands::system_path;
|
||||
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
@@ -32,6 +33,24 @@ pub fn paths_match_after_normalization(left: impl AsRef<Path>, right: impl AsRef
|
||||
left.as_ref() == right.as_ref()
|
||||
}
|
||||
|
||||
/// Replace paths equal to `old_path` and remove duplicates, preserving their order.
|
||||
///
|
||||
/// Other paths, including descendants of `old_path`, remain unchanged. This uses
|
||||
/// path equality without filesystem access or normalization; callers validate paths.
|
||||
pub fn replace_path_and_deduplicate<P>(mut paths: Vec<P>, old_path: &Path, new_path: P) -> Vec<P>
|
||||
where
|
||||
P: AsRef<Path> + Clone + Eq + Hash,
|
||||
{
|
||||
for path in &mut paths {
|
||||
if path.as_ref() == old_path {
|
||||
*path = new_path.clone();
|
||||
}
|
||||
}
|
||||
let mut seen = HashSet::new();
|
||||
paths.retain(|path| seen.insert(path.clone()));
|
||||
paths
|
||||
}
|
||||
|
||||
pub fn normalize_for_native_workdir(path: impl AsRef<Path>) -> PathBuf {
|
||||
normalize_for_native_workdir_with_flag(path.as_ref().to_path_buf(), cfg!(windows))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
use super::replace_path_and_deduplicate;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn replace_path_and_deduplicate_preserves_other_paths_and_order() {
|
||||
let old_path = Path::new("old");
|
||||
let new_path = Path::new("new");
|
||||
let extra = Path::new("extra");
|
||||
let descendant = Path::new("old/child");
|
||||
|
||||
assert_eq!(
|
||||
replace_path_and_deduplicate(
|
||||
vec![old_path, extra, descendant, new_path, extra],
|
||||
old_path,
|
||||
new_path,
|
||||
),
|
||||
vec![new_path, extra, descendant]
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod symlinks {
|
||||
use super::super::resolve_symlink_write_paths;
|
||||
|
||||
Reference in New Issue
Block a user