From 76d8f20241ad83137785cea0e29674f45472e4dd Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Fri, 12 Jun 2026 12:12:08 -0700 Subject: [PATCH] [codex] Add size to internal filesystem metadata (#27927) ## Why `ExecutorFileSystem::get_metadata` reports file kind and timestamps but not size. Internal callers that need to enforce a size limit therefore have to read the complete file first, which is especially wasteful for remote filesystems. This adds the missing internal metadata so consumers can reject oversized files before transferring or buffering them. The field is named `size`, matching VS Code's `FileStat.size` filesystem convention. ## What changed - add `size: u64` to internal `FileMetadata` - populate it from the underlying filesystem metadata - carry it through sandbox-helper and remote exec-server responses - cover files, directories, symlink targets, and sandboxed reads across local and remote filesystem implementations The new field is intentionally not exposed through the app-server API. ## Testing - `just test -p codex-exec-server get_metadata` - `just test -p codex-exec-server file_system_sandboxed_metadata_and_read_allow_readable_root` - `just test -p codex-core-plugins` - `just test -p codex-skills-extension` --- codex-rs/core-plugins/src/provider_tests.rs | 1 + codex-rs/exec-server/src/fs_helper.rs | 1 + codex-rs/exec-server/src/local_file_system.rs | 1 + codex-rs/exec-server/src/protocol.rs | 1 + .../exec-server/src/remote_file_system.rs | 1 + .../exec-server/src/sandboxed_file_system.rs | 1 + .../src/server/file_system_handler.rs | 1 + .../exec-server/tests/file_system/shared.rs | 47 ++++++++++++++++--- .../exec-server/tests/file_system_unix.rs | 29 +++++++++--- .../tests/executor_file_system_authority.rs | 1 + codex-rs/file-system/src/lib.rs | 2 + 11 files changed, 73 insertions(+), 13 deletions(-) diff --git a/codex-rs/core-plugins/src/provider_tests.rs b/codex-rs/core-plugins/src/provider_tests.rs index e2c22121b..a6a7e2fa7 100644 --- a/codex-rs/core-plugins/src/provider_tests.rs +++ b/codex-rs/core-plugins/src/provider_tests.rs @@ -129,6 +129,7 @@ impl ExecutorFileSystem for SyntheticPluginFileSystem { is_directory, is_file, is_symlink: false, + size: 0, created_at_ms: 0, modified_at_ms: 0, }) diff --git a/codex-rs/exec-server/src/fs_helper.rs b/codex-rs/exec-server/src/fs_helper.rs index 6f3dc9291..f4db844b5 100644 --- a/codex-rs/exec-server/src/fs_helper.rs +++ b/codex-rs/exec-server/src/fs_helper.rs @@ -233,6 +233,7 @@ pub(crate) async fn run_direct_request( is_directory: metadata.is_directory, is_file: metadata.is_file, is_symlink: metadata.is_symlink, + size: metadata.size, created_at_ms: metadata.created_at_ms, modified_at_ms: metadata.modified_at_ms, })) diff --git a/codex-rs/exec-server/src/local_file_system.rs b/codex-rs/exec-server/src/local_file_system.rs index 7b909cc8a..d68c7a013 100644 --- a/codex-rs/exec-server/src/local_file_system.rs +++ b/codex-rs/exec-server/src/local_file_system.rs @@ -483,6 +483,7 @@ impl DirectFileSystem { is_directory: metadata.is_dir(), is_file: metadata.is_file(), is_symlink: symlink_metadata.file_type().is_symlink(), + size: metadata.len(), created_at_ms: metadata.created().ok().map_or(0, system_time_to_unix_ms), modified_at_ms: metadata.modified().ok().map_or(0, system_time_to_unix_ms), }) diff --git a/codex-rs/exec-server/src/protocol.rs b/codex-rs/exec-server/src/protocol.rs index 387f48b35..175d23fe0 100644 --- a/codex-rs/exec-server/src/protocol.rs +++ b/codex-rs/exec-server/src/protocol.rs @@ -244,6 +244,7 @@ pub struct FsGetMetadataResponse { pub is_directory: bool, pub is_file: bool, pub is_symlink: bool, + pub size: u64, pub created_at_ms: i64, pub modified_at_ms: i64, } diff --git a/codex-rs/exec-server/src/remote_file_system.rs b/codex-rs/exec-server/src/remote_file_system.rs index f6924d780..1c5b5c8fb 100644 --- a/codex-rs/exec-server/src/remote_file_system.rs +++ b/codex-rs/exec-server/src/remote_file_system.rs @@ -132,6 +132,7 @@ impl RemoteFileSystem { is_directory: response.is_directory, is_file: response.is_file, is_symlink: response.is_symlink, + size: response.size, created_at_ms: response.created_at_ms, modified_at_ms: response.modified_at_ms, }) diff --git a/codex-rs/exec-server/src/sandboxed_file_system.rs b/codex-rs/exec-server/src/sandboxed_file_system.rs index 94d18aafc..f1ed02f76 100644 --- a/codex-rs/exec-server/src/sandboxed_file_system.rs +++ b/codex-rs/exec-server/src/sandboxed_file_system.rs @@ -164,6 +164,7 @@ impl SandboxedFileSystem { is_directory: response.is_directory, is_file: response.is_file, is_symlink: response.is_symlink, + size: response.size, created_at_ms: response.created_at_ms, modified_at_ms: response.modified_at_ms, }) 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 211685c13..080d4829d 100644 --- a/codex-rs/exec-server/src/server/file_system_handler.rs +++ b/codex-rs/exec-server/src/server/file_system_handler.rs @@ -103,6 +103,7 @@ impl FileSystemHandler { is_directory: metadata.is_directory, is_file: metadata.is_file, is_symlink: metadata.is_symlink, + size: metadata.size, created_at_ms: metadata.created_at_ms, modified_at_ms: metadata.modified_at_ms, }) diff --git a/codex-rs/exec-server/tests/file_system/shared.rs b/codex-rs/exec-server/tests/file_system/shared.rs index 0bef4aa28..27bdb22d8 100644 --- a/codex-rs/exec-server/tests/file_system/shared.rs +++ b/codex-rs/exec-server/tests/file_system/shared.rs @@ -2,6 +2,7 @@ use anyhow::Context; use anyhow::Result; use codex_exec_server::CopyOptions; use codex_exec_server::CreateDirectoryOptions; +use codex_exec_server::FileMetadata; use codex_exec_server::ReadDirectoryEntry; use codex_exec_server::RemoveOptions; use codex_protocol::models::AdditionalPermissionProfile; @@ -65,18 +66,34 @@ async fn file_system_get_metadata_reports_files_and_directories( .get_metadata(&PathUri::from_path(&file_path)?, /*sandbox*/ None) .await .with_context(|| format!("mode={implementation}"))?; - assert_eq!(file_metadata.is_directory, false); - assert_eq!(file_metadata.is_file, true); - assert_eq!(file_metadata.is_symlink, false); + assert_eq!( + file_metadata, + FileMetadata { + is_directory: false, + is_file: true, + is_symlink: false, + size: 5, + created_at_ms: file_metadata.created_at_ms, + modified_at_ms: file_metadata.modified_at_ms, + } + ); assert!(file_metadata.modified_at_ms > 0); let directory_metadata = file_system .get_metadata(&PathUri::from_path(&directory_path)?, /*sandbox*/ None) .await .with_context(|| format!("mode={implementation}"))?; - assert_eq!(directory_metadata.is_directory, true); - assert_eq!(directory_metadata.is_file, false); - assert_eq!(directory_metadata.is_symlink, false); + assert_eq!( + directory_metadata, + FileMetadata { + is_directory: true, + is_file: false, + is_symlink: false, + size: std::fs::metadata(&directory_path)?.len(), + created_at_ms: directory_metadata.created_at_ms, + modified_at_ms: directory_metadata.modified_at_ms, + } + ); assert!(directory_metadata.modified_at_ms > 0); Ok(()) @@ -395,7 +412,7 @@ async fn file_system_copy_rejects_directory_without_recursive( #[test_case(FileSystemImplementation::Local ; "local")] #[test_case(FileSystemImplementation::Remote ; "remote")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn file_system_sandboxed_read_allows_readable_root( +async fn file_system_sandboxed_metadata_and_read_allow_readable_root( implementation: FileSystemImplementation, ) -> Result<()> { let context = create_file_system_context(implementation).await?; @@ -408,6 +425,22 @@ async fn file_system_sandboxed_read_allows_readable_root( std::fs::write(&file_path, "sandboxed hello")?; let sandbox = read_only_sandbox(allowed_dir); + let metadata = file_system + .get_metadata(&PathUri::from_path(&file_path)?, Some(&sandbox)) + .await + .with_context(|| format!("mode={implementation}"))?; + assert_eq!( + metadata, + FileMetadata { + is_directory: false, + is_file: true, + is_symlink: false, + size: 15, + created_at_ms: metadata.created_at_ms, + modified_at_ms: metadata.modified_at_ms, + } + ); + let contents = file_system .read_file(&PathUri::from_path(&file_path)?, Some(&sandbox)) .await diff --git a/codex-rs/exec-server/tests/file_system_unix.rs b/codex-rs/exec-server/tests/file_system_unix.rs index 3538f3236..3c4dfa99a 100644 --- a/codex-rs/exec-server/tests/file_system_unix.rs +++ b/codex-rs/exec-server/tests/file_system_unix.rs @@ -21,6 +21,7 @@ use codex_exec_server::CopyOptions; use codex_exec_server::CreateDirectoryOptions; #[cfg(target_os = "linux")] use codex_exec_server::Environment; +use codex_exec_server::FileMetadata; use codex_exec_server::RemoveOptions; use codex_utils_path_uri::PathUri; use pretty_assertions::assert_eq; @@ -222,9 +223,17 @@ async fn file_system_get_metadata_reports_symlink_targets( .get_metadata(&PathUri::from_path(&symlink_path)?, /*sandbox*/ None) .await .with_context(|| format!("mode={implementation}"))?; - assert_eq!(symlink_metadata.is_directory, false); - assert_eq!(symlink_metadata.is_file, true); - assert_eq!(symlink_metadata.is_symlink, true); + assert_eq!( + symlink_metadata, + FileMetadata { + is_directory: false, + is_file: true, + is_symlink: true, + size: 5, + created_at_ms: symlink_metadata.created_at_ms, + modified_at_ms: symlink_metadata.modified_at_ms, + } + ); assert!(symlink_metadata.modified_at_ms > 0); let dir_path = tmp.path().join("notes"); @@ -238,9 +247,17 @@ async fn file_system_get_metadata_reports_symlink_targets( ) .await .with_context(|| format!("mode={implementation}"))?; - assert_eq!(dir_symlink_metadata.is_directory, true); - assert_eq!(dir_symlink_metadata.is_file, false); - assert_eq!(dir_symlink_metadata.is_symlink, true); + assert_eq!( + dir_symlink_metadata, + FileMetadata { + is_directory: true, + is_file: false, + is_symlink: true, + size: std::fs::metadata(&dir_path)?.len(), + created_at_ms: dir_symlink_metadata.created_at_ms, + modified_at_ms: dir_symlink_metadata.modified_at_ms, + } + ); Ok(()) } 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 d5387abce..b57f1db6b 100644 --- a/codex-rs/ext/skills/tests/executor_file_system_authority.rs +++ b/codex-rs/ext/skills/tests/executor_file_system_authority.rs @@ -85,6 +85,7 @@ impl SyntheticFileSystem { is_directory, is_file, is_symlink: false, + size: 0, created_at_ms: 0, modified_at_ms: 0, }) diff --git a/codex-rs/file-system/src/lib.rs b/codex-rs/file-system/src/lib.rs index 929162b10..429c2ead5 100644 --- a/codex-rs/file-system/src/lib.rs +++ b/codex-rs/file-system/src/lib.rs @@ -34,6 +34,8 @@ pub struct FileMetadata { pub is_directory: bool, pub is_file: bool, pub is_symlink: bool, + /// Size in bytes. + pub size: u64, pub created_at_ms: i64, pub modified_at_ms: i64, }