Add a bounded filesystem walk RPC (#29841)

Stack 1 of 3. Follow-ups: #29842 and #29844.

## What changes

Adds a general bounded `fs/walk` operation to the exec server.

The operation returns file and directory entries plus recoverable
per-path errors. It skips symlinks, preserves the existing filesystem
sandbox routing, and enforces depth, directory, entry, and response-size
limits.

This PR only defines and wires the filesystem operation. It does not
change any callers yet.
This commit is contained in:
jif
2026-06-24 16:05:43 +01:00
committed by GitHub
parent b4f0f3eff1
commit c14623d04c
13 changed files with 608 additions and 0 deletions
@@ -6,6 +6,10 @@ use codex_exec_server::FILE_READ_CHUNK_SIZE;
use codex_exec_server::FileMetadata;
use codex_exec_server::ReadDirectoryEntry;
use codex_exec_server::RemoveOptions;
use codex_exec_server::WalkEntry;
use codex_exec_server::WalkEntryKind;
use codex_exec_server::WalkOptions;
use codex_exec_server::WalkOutcome;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::PermissionProfile;
@@ -373,6 +377,179 @@ async fn file_system_read_directory_lists_entries(
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_walk_returns_a_bounded_tree(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let source_dir = tmp.path().join("source");
let nested_dir = source_dir.join("nested");
std::fs::create_dir_all(&nested_dir)?;
std::fs::write(source_dir.join("root.txt"), "root")?;
std::fs::write(nested_dir.join("note.txt"), "nested")?;
let source_uri = PathUri::from_host_native_path(&source_dir)?;
let outcome = file_system
.walk(
&source_uri,
WalkOptions {
max_depth: 4,
max_directories: 10,
max_entries: 10,
},
/*sandbox*/ None,
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(
outcome,
WalkOutcome {
entries: vec![
WalkEntry {
path: PathUri::from_host_native_path(&nested_dir)?,
kind: WalkEntryKind::Directory,
},
WalkEntry {
path: PathUri::from_host_native_path(source_dir.join("root.txt"))?,
kind: WalkEntryKind::File,
},
WalkEntry {
path: PathUri::from_host_native_path(nested_dir.join("note.txt"))?,
kind: WalkEntryKind::File,
},
],
errors: Vec::new(),
truncated: false,
}
);
let root_entries = vec![
WalkEntry {
path: PathUri::from_host_native_path(&nested_dir)?,
kind: WalkEntryKind::Directory,
},
WalkEntry {
path: PathUri::from_host_native_path(source_dir.join("root.txt"))?,
kind: WalkEntryKind::File,
},
];
let shallow = file_system
.walk(
&source_uri,
WalkOptions {
max_depth: 0,
max_directories: 10,
max_entries: 10,
},
/*sandbox*/ None,
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(
shallow,
WalkOutcome {
entries: root_entries.clone(),
errors: Vec::new(),
truncated: false,
}
);
let directory_bounded = file_system
.walk(
&source_uri,
WalkOptions {
max_depth: 4,
max_directories: 1,
max_entries: 10,
},
/*sandbox*/ None,
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(
directory_bounded,
WalkOutcome {
entries: root_entries,
errors: Vec::new(),
truncated: true,
}
);
let bounded = file_system
.walk(
&source_uri,
WalkOptions {
max_depth: 4,
max_directories: 10,
max_entries: 1,
},
/*sandbox*/ None,
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(
bounded,
WalkOutcome {
entries: vec![WalkEntry {
path: PathUri::from_host_native_path(&nested_dir)?,
kind: WalkEntryKind::Directory,
}],
errors: Vec::new(),
truncated: true,
}
);
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_walk_honors_read_sandbox(
implementation: FileSystemImplementation,
) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let source_dir = tmp.path().join("source");
let file_path = source_dir.join("note.txt");
std::fs::create_dir_all(&source_dir)?;
std::fs::write(&file_path, "sandboxed")?;
let sandbox = read_only_sandbox(source_dir.clone());
let outcome = file_system
.walk(
&PathUri::from_host_native_path(&source_dir)?,
WalkOptions {
max_depth: 1,
max_directories: 2,
max_entries: 2,
},
Some(&sandbox),
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(
outcome,
WalkOutcome {
entries: vec![WalkEntry {
path: PathUri::from_host_native_path(file_path)?,
kind: WalkEntryKind::File,
}],
errors: Vec::new(),
truncated: false,
}
);
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -24,6 +24,10 @@ use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::Environment;
use codex_exec_server::FileMetadata;
use codex_exec_server::RemoveOptions;
use codex_exec_server::WalkEntry;
use codex_exec_server::WalkEntryKind;
use codex_exec_server::WalkOptions;
use codex_exec_server::WalkOutcome;
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
@@ -266,6 +270,54 @@ async fn file_system_get_metadata_reports_symlink_targets(
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn file_system_walk_ignores_symlinks(implementation: FileSystemImplementation) -> Result<()> {
let context = create_file_system_context(implementation).await?;
let file_system = context.file_system;
let tmp = TempDir::new()?;
let root = tmp.path().join("root");
let target = root.join("target");
let target_file = target.join("note.txt");
std::fs::create_dir_all(&target)?;
std::fs::write(&target_file, "target")?;
symlink(&target, root.join("target-link"))?;
let outcome = file_system
.walk(
&PathUri::from_host_native_path(&root)?,
WalkOptions {
max_depth: 2,
max_directories: 4,
max_entries: 8,
},
/*sandbox*/ None,
)
.await
.with_context(|| format!("mode={implementation}"))?;
assert_eq!(
outcome,
WalkOutcome {
entries: vec![
WalkEntry {
path: PathUri::from_host_native_path(&target)?,
kind: WalkEntryKind::Directory,
},
WalkEntry {
path: PathUri::from_host_native_path(target_file)?,
kind: WalkEntryKind::File,
},
],
errors: Vec::new(),
truncated: false,
}
);
Ok(())
}
#[test_case(FileSystemImplementation::Local ; "local")]
#[test_case(FileSystemImplementation::Remote ; "remote")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]