diff --git a/codex-rs/app-server/src/request_processors/fs_processor.rs b/codex-rs/app-server/src/request_processors/fs_processor.rs index b16209a5ac..0e1407c5a7 100644 --- a/codex-rs/app-server/src/request_processors/fs_processor.rs +++ b/codex-rs/app-server/src/request_processors/fs_processor.rs @@ -115,9 +115,10 @@ impl FsRequestProcessor { &self, params: FsGetMetadataParams, ) -> Result { + let path = PathUri::from_abs_path(¶ms.path).map_err(map_fs_error)?; let metadata = self .file_system()? - .get_metadata(¶ms.path, /*sandbox*/ None) + .get_metadata(&path, /*sandbox*/ None) .await .map_err(map_fs_error)?; Ok(FsGetMetadataResponse { diff --git a/codex-rs/apply-patch/src/lib.rs b/codex-rs/apply-patch/src/lib.rs index 705774ca17..b43771ce27 100644 --- a/codex-rs/apply-patch/src/lib.rs +++ b/codex-rs/apply-patch/src/lib.rs @@ -556,7 +556,8 @@ async fn ensure_not_directory( fs: &dyn ExecutorFileSystem, sandbox: Option<&FileSystemSandboxContext>, ) -> io::Result<()> { - let metadata = fs.get_metadata(path, sandbox).await?; + let path_uri = PathUri::from_abs_path(path)?; + let metadata = fs.get_metadata(&path_uri, sandbox).await?; if metadata.is_directory { return Err(io::Error::new( io::ErrorKind::InvalidInput, @@ -614,7 +615,11 @@ async fn note_existing_path_delta_support( sandbox: Option<&FileSystemSandboxContext>, exact: &mut bool, ) { - match fs.get_metadata(path, sandbox).await { + let Ok(path_uri) = PathUri::from_abs_path(path) else { + *exact = false; + return; + }; + match fs.get_metadata(&path_uri, sandbox).await { Ok(metadata) if metadata.is_file && !metadata.is_symlink => {} Ok(_) => *exact = false, Err(source) if source.kind() == io::ErrorKind::NotFound => {} diff --git a/codex-rs/config/src/loader/mod.rs b/codex-rs/config/src/loader/mod.rs index 9501d67250..d47ad50b26 100644 --- a/codex-rs/config/src/loader/mod.rs +++ b/codex-rs/config/src/loader/mod.rs @@ -1138,8 +1138,9 @@ async fn find_project_root( for ancestor in cwd.ancestors() { for marker in project_root_markers { let marker_path = ancestor.join(marker); + let marker_path_uri = PathUri::from_abs_path(&marker_path)?; if fs - .get_metadata(&marker_path, /*sandbox*/ None) + .get_metadata(&marker_path_uri, /*sandbox*/ None) .await .is_ok() { @@ -1154,14 +1155,20 @@ async fn find_git_checkout_root( fs: &dyn ExecutorFileSystem, cwd: &AbsolutePathBuf, ) -> Option { - let base = match fs.get_metadata(cwd, /*sandbox*/ None).await { + let cwd_uri = PathUri::from_abs_path(cwd).ok()?; + let base = match fs.get_metadata(&cwd_uri, /*sandbox*/ None).await { Ok(metadata) if metadata.is_directory => cwd.clone(), _ => cwd.parent()?, }; for dir in base.ancestors() { let dot_git = dir.join(".git"); - if fs.get_metadata(&dot_git, /*sandbox*/ None).await.is_ok() { + let dot_git_uri = PathUri::from_abs_path(&dot_git).ok()?; + if fs + .get_metadata(&dot_git_uri, /*sandbox*/ None) + .await + .is_ok() + { return Some(dir); } } @@ -1209,8 +1216,9 @@ async fn load_project_layers( let mut startup_warnings = Vec::new(); for dir in dirs { let dot_codex_abs = dir.join(".codex"); + let dot_codex_uri = PathUri::from_abs_path(&dot_codex_abs)?; if !fs - .get_metadata(&dot_codex_abs, /*sandbox*/ None) + .get_metadata(&dot_codex_uri, /*sandbox*/ None) .await .map(|metadata| metadata.is_directory) .unwrap_or(false) diff --git a/codex-rs/config/src/loader/tests.rs b/codex-rs/config/src/loader/tests.rs index e1f91074bf..b48034ccea 100644 --- a/codex-rs/config/src/loader/tests.rs +++ b/codex-rs/config/src/loader/tests.rs @@ -54,7 +54,7 @@ impl ExecutorFileSystem for TestFileSystem { async fn get_metadata( &self, - _path: &AbsolutePathBuf, + _path: &PathUri, _sandbox: Option<&FileSystemSandboxContext>, ) -> FileSystemResult { unimplemented!("test filesystem only supports reads") diff --git a/codex-rs/core-skills/src/loader.rs b/codex-rs/core-skills/src/loader.rs index 5a3c218007..ebb1bdbae1 100644 --- a/codex-rs/core-skills/src/loader.rs +++ b/codex-rs/core-skills/src/loader.rs @@ -375,7 +375,17 @@ async fn repo_agents_skill_roots( let mut roots = Vec::new(); for dir in dirs { let agents_skills = dir.join(AGENTS_DIR_NAME).join(SKILLS_DIR_NAME); - match fs.get_metadata(&agents_skills, /*sandbox*/ None).await { + let agents_skills_uri = match PathUri::from_abs_path(&agents_skills) { + Ok(path) => path, + Err(err) => { + tracing::warn!( + "failed to convert repo skills root {} to URI: {err:#}", + agents_skills.display() + ); + continue; + } + }; + match fs.get_metadata(&agents_skills_uri, /*sandbox*/ None).await { Ok(metadata) if metadata.is_directory => roots.push(SkillRoot { path: agents_skills, scope: SkillScope::Repo, @@ -430,7 +440,17 @@ async fn find_project_root( for ancestor in cwd.ancestors() { for marker in project_root_markers { let marker_path = ancestor.join(marker); - match fs.get_metadata(&marker_path, /*sandbox*/ None).await { + let marker_path_uri = match PathUri::from_abs_path(&marker_path) { + Ok(path) => path, + Err(err) => { + tracing::warn!( + "failed to convert project root marker {} to URI: {err:#}", + marker_path.display() + ); + continue; + } + }; + match fs.get_metadata(&marker_path_uri, /*sandbox*/ None).await { Ok(_) => return ancestor, Err(err) if err.kind() == io::ErrorKind::NotFound => {} Err(err) => { @@ -499,7 +519,17 @@ async fn discover_skills_under_root( None => None, }; - match fs.get_metadata(&root, /*sandbox*/ None).await { + let root_uri = match PathUri::from_abs_path(&root) { + Ok(path) => path, + Err(err) => { + tracing::warn!( + "failed to convert skills root {} to URI: {err:#}", + root.display() + ); + return; + } + }; + match fs.get_metadata(&root_uri, /*sandbox*/ None).await { Ok(metadata) if metadata.is_directory => {} Ok(_) => return, Err(err) if err.kind() == io::ErrorKind::NotFound => return, @@ -556,7 +586,17 @@ async fn discover_skills_under_root( } let path = dir.join(&file_name); - let metadata = match fs.get_metadata(&path, /*sandbox*/ None).await { + let path_uri = match PathUri::from_abs_path(&path) { + Ok(path) => path, + Err(e) => { + tracing::warn!( + "failed to convert skills path {} to URI: {e:#}", + path.display() + ); + continue; + } + }; + let metadata = match fs.get_metadata(&path_uri, /*sandbox*/ None).await { Ok(metadata) => metadata, Err(e) => { error!("failed to stat skills path {}: {e:#}", path.display()); @@ -747,7 +787,7 @@ async fn load_skill_metadata( return LoadedSkillMetadata::default(); } }; - match fs.get_metadata(&metadata_path, /*sandbox*/ None).await { + match fs.get_metadata(&metadata_path_uri, /*sandbox*/ None).await { Ok(metadata) if metadata.is_file => {} Ok(_) => return LoadedSkillMetadata::default(), Err(error) if error.kind() == io::ErrorKind::NotFound => { diff --git a/codex-rs/core/src/agents_md.rs b/codex-rs/core/src/agents_md.rs index 70b51fb32a..e60eacc55c 100644 --- a/codex-rs/core/src/agents_md.rs +++ b/codex-rs/core/src/agents_md.rs @@ -163,7 +163,7 @@ impl<'a> AgentsMdManager<'a> { } let path_uri = PathUri::from_abs_path(&p)?; - match fs.get_metadata(&p, /*sandbox*/ None).await { + match fs.get_metadata(&path_uri, /*sandbox*/ None).await { Ok(metadata) if !metadata.is_file => continue, Ok(_) => {} Err(err) if err.kind() == io::ErrorKind::NotFound => continue, @@ -245,12 +245,13 @@ impl<'a> AgentsMdManager<'a> { for ancestor in dir.ancestors() { for marker in &project_root_markers { let marker_path = ancestor.join(marker); - let marker_exists = match fs.get_metadata(&marker_path, /*sandbox*/ None).await - { - Ok(_) => true, - Err(err) if err.kind() == io::ErrorKind::NotFound => false, - Err(err) => return Err(err), - }; + let marker_path_uri = PathUri::from_abs_path(&marker_path)?; + let marker_exists = + match fs.get_metadata(&marker_path_uri, /*sandbox*/ None).await { + Ok(_) => true, + Err(err) if err.kind() == io::ErrorKind::NotFound => false, + Err(err) => return Err(err), + }; if marker_exists { project_root = Some(ancestor.clone()); break; @@ -286,7 +287,8 @@ impl<'a> AgentsMdManager<'a> { for d in search_dirs { for name in &candidate_filenames { let candidate = d.join(name); - match fs.get_metadata(&candidate, /*sandbox*/ None).await { + let candidate_uri = PathUri::from_abs_path(&candidate)?; + match fs.get_metadata(&candidate_uri, /*sandbox*/ None).await { Ok(md) if md.is_file => { found.push(candidate); break; diff --git a/codex-rs/core/src/config/agent_roles.rs b/codex-rs/core/src/config/agent_roles.rs index 269f5a54cd..b428f02bc8 100644 --- a/codex-rs/core/src/config/agent_roles.rs +++ b/codex-rs/core/src/config/agent_roles.rs @@ -393,8 +393,9 @@ async fn validate_agent_role_config_file( return Ok(()); }; + let config_file_uri = PathUri::from_abs_path(config_file)?; let metadata = fs - .get_metadata(config_file, /*sandbox*/ None) + .get_metadata(&config_file_uri, /*sandbox*/ None) .await .map_err(|e| { std::io::Error::new( diff --git a/codex-rs/core/src/tools/handlers/view_image.rs b/codex-rs/core/src/tools/handlers/view_image.rs index 8f7dd509f9..900879f253 100644 --- a/codex-rs/core/src/tools/handlers/view_image.rs +++ b/codex-rs/core/src/tools/handlers/view_image.rs @@ -153,7 +153,7 @@ impl ViewImageHandler { })?; let metadata = fs - .get_metadata(&abs_path, Some(&sandbox)) + .get_metadata(&path_uri, Some(&sandbox)) .await .map_err(|error| { FunctionCallError::RespondToModel(format!( diff --git a/codex-rs/core/tests/common/test_codex.rs b/codex-rs/core/tests/common/test_codex.rs index a06ef88998..fbd835b17a 100644 --- a/codex-rs/core/tests/common/test_codex.rs +++ b/codex-rs/core/tests/common/test_codex.rs @@ -975,7 +975,13 @@ impl TestCodexHarness { } pub async fn abs_path_exists(&self, path: &AbsolutePathBuf) -> Result { - match self.test.fs().get_metadata(path, /*sandbox*/ None).await { + let path_uri = PathUri::from_abs_path(path)?; + match self + .test + .fs() + .get_metadata(&path_uri, /*sandbox*/ None) + .await + { Ok(_) => Ok(true), Err(err) if err.kind() == ErrorKind::NotFound => Ok(false), Err(err) => Err(err.into()), diff --git a/codex-rs/core/tests/suite/remote_env.rs b/codex-rs/core/tests/suite/remote_env.rs index 8e64c0fa57..c3b09bcb5f 100644 --- a/codex-rs/core/tests/suite/remote_env.rs +++ b/codex-rs/core/tests/suite/remote_env.rs @@ -1054,7 +1054,10 @@ async fn remote_test_env_remove_removes_symlink_not_target() -> Result<()> { .await?; let symlink_exists = file_system - .get_metadata(&absolute_path(symlink_path), /*sandbox*/ None) + .get_metadata( + &PathUri::from_abs_path(&absolute_path(symlink_path))?, + /*sandbox*/ None, + ) .await .is_ok(); assert!(!symlink_exists); diff --git a/codex-rs/exec-server/src/fs_helper.rs b/codex-rs/exec-server/src/fs_helper.rs index 8cfd89237d..763d325030 100644 --- a/codex-rs/exec-server/src/fs_helper.rs +++ b/codex-rs/exec-server/src/fs_helper.rs @@ -231,8 +231,10 @@ pub(crate) async fn run_direct_request( )) } FsHelperRequest::GetMetadata(params) => { + let path = + codex_utils_path_uri::PathUri::from_abs_path(¶ms.path).map_err(map_fs_error)?; let metadata = file_system - .get_metadata(¶ms.path, /*sandbox*/ None) + .get_metadata(&path, /*sandbox*/ None) .await .map_err(map_fs_error)?; Ok(FsHelperPayload::GetMetadata(FsGetMetadataResponse { diff --git a/codex-rs/exec-server/src/local_file_system.rs b/codex-rs/exec-server/src/local_file_system.rs index a88bb670dc..4a11160f75 100644 --- a/codex-rs/exec-server/src/local_file_system.rs +++ b/codex-rs/exec-server/src/local_file_system.rs @@ -120,7 +120,7 @@ impl ExecutorFileSystem for LocalFileSystem { async fn get_metadata( &self, - path: &AbsolutePathBuf, + path: &PathUri, sandbox: Option<&FileSystemSandboxContext>, ) -> FileSystemResult { let (file_system, sandbox) = self.file_system_for(sandbox)?; @@ -206,7 +206,7 @@ impl ExecutorFileSystem for UnsandboxedFileSystem { async fn get_metadata( &self, - path: &AbsolutePathBuf, + path: &PathUri, sandbox: Option<&FileSystemSandboxContext>, ) -> FileSystemResult { reject_platform_sandbox_context(sandbox)?; @@ -315,10 +315,11 @@ impl ExecutorFileSystem for DirectFileSystem { async fn get_metadata( &self, - path: &AbsolutePathBuf, + path: &PathUri, sandbox: Option<&FileSystemSandboxContext>, ) -> FileSystemResult { reject_sandbox_context(sandbox)?; + let path = path.to_abs_path()?; let metadata = tokio::fs::metadata(path.as_path()).await?; let symlink_metadata = tokio::fs::symlink_metadata(path.as_path()).await?; Ok(FileMetadata { diff --git a/codex-rs/exec-server/src/remote_file_system.rs b/codex-rs/exec-server/src/remote_file_system.rs index 943555ad97..ff594849df 100644 --- a/codex-rs/exec-server/src/remote_file_system.rs +++ b/codex-rs/exec-server/src/remote_file_system.rs @@ -124,14 +124,15 @@ impl ExecutorFileSystem for RemoteFileSystem { async fn get_metadata( &self, - path: &AbsolutePathBuf, + path: &PathUri, sandbox: Option<&FileSystemSandboxContext>, ) -> FileSystemResult { trace!("remote fs get_metadata"); + let path = path.to_abs_path()?; let client = self.client.get().await.map_err(map_remote_error)?; let response = client .fs_get_metadata(FsGetMetadataParams { - path: path.clone(), + path, sandbox: remote_sandbox_context(sandbox), }) .await diff --git a/codex-rs/exec-server/src/sandboxed_file_system.rs b/codex-rs/exec-server/src/sandboxed_file_system.rs index e38361547e..9d5dce32d1 100644 --- a/codex-rs/exec-server/src/sandboxed_file_system.rs +++ b/codex-rs/exec-server/src/sandboxed_file_system.rs @@ -142,7 +142,7 @@ impl ExecutorFileSystem for SandboxedFileSystem { async fn get_metadata( &self, - path: &AbsolutePathBuf, + path: &PathUri, sandbox: Option<&FileSystemSandboxContext>, ) -> FileSystemResult { let sandbox = require_platform_sandbox(sandbox)?; @@ -150,7 +150,7 @@ impl ExecutorFileSystem for SandboxedFileSystem { .run_sandboxed( sandbox, FsHelperRequest::GetMetadata(FsGetMetadataParams { - path: path.clone(), + path: path.to_abs_path()?, sandbox: None, }), ) diff --git a/codex-rs/exec-server/src/server/file_system_handler.rs b/codex-rs/exec-server/src/server/file_system_handler.rs index c1f5f46530..5821dce026 100644 --- a/codex-rs/exec-server/src/server/file_system_handler.rs +++ b/codex-rs/exec-server/src/server/file_system_handler.rs @@ -102,9 +102,10 @@ impl FileSystemHandler { &self, params: FsGetMetadataParams, ) -> Result { + let path = PathUri::from_abs_path(¶ms.path).map_err(map_fs_error)?; let metadata = self .file_system - .get_metadata(¶ms.path, params.sandbox.as_ref()) + .get_metadata(&path, params.sandbox.as_ref()) .await .map_err(map_fs_error)?; Ok(FsGetMetadataResponse { diff --git a/codex-rs/exec-server/tests/file_system/shared.rs b/codex-rs/exec-server/tests/file_system/shared.rs index 24aa1f4f54..76d0e5115e 100644 --- a/codex-rs/exec-server/tests/file_system/shared.rs +++ b/codex-rs/exec-server/tests/file_system/shared.rs @@ -62,7 +62,7 @@ async fn file_system_get_metadata_reports_files_and_directories( std::fs::create_dir(&directory_path)?; let file_metadata = file_system - .get_metadata(&absolute_path(&file_path), /*sandbox*/ None) + .get_metadata(&PathUri::from_path(&file_path)?, /*sandbox*/ None) .await .with_context(|| format!("mode={implementation}"))?; assert_eq!(file_metadata.is_directory, false); @@ -71,7 +71,7 @@ async fn file_system_get_metadata_reports_files_and_directories( assert!(file_metadata.modified_at_ms > 0); let directory_metadata = file_system - .get_metadata(&absolute_path(&directory_path), /*sandbox*/ None) + .get_metadata(&PathUri::from_path(&directory_path)?, /*sandbox*/ None) .await .with_context(|| format!("mode={implementation}"))?; assert_eq!(directory_metadata.is_directory, true); diff --git a/codex-rs/exec-server/tests/file_system_unix.rs b/codex-rs/exec-server/tests/file_system_unix.rs index d7d1397e94..29cb6fc8a6 100644 --- a/codex-rs/exec-server/tests/file_system_unix.rs +++ b/codex-rs/exec-server/tests/file_system_unix.rs @@ -220,7 +220,7 @@ async fn file_system_get_metadata_reports_symlink_targets( let symlink_path = tmp.path().join("note-link.txt"); symlink(&file_path, &symlink_path)?; let symlink_metadata = file_system - .get_metadata(&absolute_path(&symlink_path), /*sandbox*/ None) + .get_metadata(&PathUri::from_path(&symlink_path)?, /*sandbox*/ None) .await .with_context(|| format!("mode={implementation}"))?; assert_eq!(symlink_metadata.is_directory, false); @@ -233,7 +233,10 @@ async fn file_system_get_metadata_reports_symlink_targets( let dir_symlink_path = tmp.path().join("notes-link"); symlink(&dir_path, &dir_symlink_path)?; let dir_symlink_metadata = file_system - .get_metadata(&absolute_path(&dir_symlink_path), /*sandbox*/ None) + .get_metadata( + &PathUri::from_path(&dir_symlink_path)?, + /*sandbox*/ None, + ) .await .with_context(|| format!("mode={implementation}"))?; assert_eq!(dir_symlink_metadata.is_directory, true); diff --git a/codex-rs/ext/skills/tests/executor_file_system_authority.rs b/codex-rs/ext/skills/tests/executor_file_system_authority.rs index 2808b724fa..111d3df6a4 100644 --- a/codex-rs/ext/skills/tests/executor_file_system_authority.rs +++ b/codex-rs/ext/skills/tests/executor_file_system_authority.rs @@ -103,10 +103,10 @@ impl ExecutorFileSystem for SyntheticFileSystem { async fn get_metadata( &self, - path: &AbsolutePathBuf, + path: &PathUri, _sandbox: Option<&FileSystemSandboxContext>, ) -> FileSystemResult { - self.metadata(path) + self.metadata(&path.to_abs_path()?) } async fn read_directory( diff --git a/codex-rs/file-system/src/lib.rs b/codex-rs/file-system/src/lib.rs index a83f055c34..a3f5218b0d 100644 --- a/codex-rs/file-system/src/lib.rs +++ b/codex-rs/file-system/src/lib.rs @@ -173,7 +173,7 @@ pub trait ExecutorFileSystem: Send + Sync { async fn get_metadata( &self, - path: &AbsolutePathBuf, + path: &PathUri, sandbox: Option<&FileSystemSandboxContext>, ) -> FileSystemResult; diff --git a/codex-rs/git-utils/src/info.rs b/codex-rs/git-utils/src/info.rs index aa581e49ae..beff18b519 100644 --- a/codex-rs/git-utils/src/info.rs +++ b/codex-rs/git-utils/src/info.rs @@ -47,7 +47,8 @@ pub async fn get_git_repo_root_with_fs( fs: &dyn ExecutorFileSystem, cwd: &AbsolutePathBuf, ) -> Option { - let base = match fs.get_metadata(cwd, /*sandbox*/ None).await { + let cwd_uri = PathUri::from_abs_path(cwd).ok()?; + let base = match fs.get_metadata(&cwd_uri, /*sandbox*/ None).await { Ok(metadata) if metadata.is_directory => cwd.clone(), _ => cwd.parent()?, }; @@ -806,7 +807,7 @@ pub async fn resolve_root_git_project_for_trust( let dot_git = repo_root.join(".git"); let dot_git_uri = PathUri::from_abs_path(&dot_git).ok()?; if fs - .get_metadata(&dot_git, /*sandbox*/ None) + .get_metadata(&dot_git_uri, /*sandbox*/ None) .await .ok()? .is_directory @@ -858,7 +859,12 @@ async fn find_ancestor_git_entry_with_fs( ) -> Option<(AbsolutePathBuf, AbsolutePathBuf)> { for dir in base_dir.ancestors() { let dot_git = dir.join(".git"); - if fs.get_metadata(&dot_git, /*sandbox*/ None).await.is_ok() { + let dot_git_uri = PathUri::from_abs_path(&dot_git).ok()?; + if fs + .get_metadata(&dot_git_uri, /*sandbox*/ None) + .await + .is_ok() + { return Some((dir, dot_git)); } } diff --git a/codex-rs/utils/plugins/src/plugin_namespace.rs b/codex-rs/utils/plugins/src/plugin_namespace.rs index 8604c87b30..d0d8bf685e 100644 --- a/codex-rs/utils/plugins/src/plugin_namespace.rs +++ b/codex-rs/utils/plugins/src/plugin_namespace.rs @@ -30,7 +30,8 @@ async fn plugin_manifest_name( let mut manifest_path = None; for relative_path in DISCOVERABLE_PLUGIN_MANIFEST_PATHS { let candidate = plugin_root.join(relative_path); - match fs.get_metadata(&candidate, /*sandbox*/ None).await { + let candidate_uri = PathUri::from_abs_path(&candidate).ok()?; + match fs.get_metadata(&candidate_uri, /*sandbox*/ None).await { Ok(metadata) if metadata.is_file => { manifest_path = Some(candidate); break;