Use fs/walk for environment skill discovery (#29842)

Stack 2 of 3. Base: #29841. Follow-up: #29844.

## What changes

Environment skill discovery currently walks remote filesystems through
repeated `readDirectory` and `getMetadata` calls. This switches that
scan to the bounded `fs/walk` operation from the base PR.

```text
Before: readDirectory(root) -> getMetadata(...) -> readDirectory(child) -> ...
After:  fs/walk(root, limits) -> filter the result for SKILL.md
```

This makes environment skill discovery one RPC while preserving
traversal warnings and the existing depth and directory limits. The scan
also has an explicit entry limit. The follow-up restores
directory-symlink traversal.
This commit is contained in:
jif
2026-06-24 16:32:35 +01:00
committed by GitHub
Unverified
parent c14623d04c
commit 69b76e9d07
+58 -4
View File
@@ -1,6 +1,8 @@
use std::io;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::WalkEntryKind;
use codex_exec_server::WalkOptions;
use codex_protocol::protocol::Product;
use codex_utils_path_uri::PathUri;
use codex_utils_plugins::plugin_namespace_for_skill_uri;
@@ -9,18 +11,22 @@ use crate::model::SkillDependencies;
use crate::model::SkillPolicy;
use super::MAX_QUALIFIED_NAME_LEN;
use super::MAX_SCAN_DEPTH;
use super::MAX_SKILLS_DIRS_PER_ROOT;
use super::ParsedSkillFrontmatter;
use super::SKILLS_FILENAME;
use super::SKILLS_METADATA_DIR;
use super::SKILLS_METADATA_FILENAME;
use super::SkillFileDiscovery;
use super::SkillMetadataFile;
use super::SymlinkPolicy;
use super::discover_skills_under_root;
use super::parse_skill_frontmatter_metadata_inner;
use super::resolve_dependencies;
use super::resolve_policy;
use super::sanitize_single_line;
use super::validate_len;
const MAX_SKILLS_ENTRIES_PER_ROOT: usize = 20_000;
/// URI-native metadata for one skill owned by an execution environment.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EnvironmentSkillMetadata {
@@ -95,8 +101,56 @@ pub async fn load_environment_skills_from_root(
restriction_product: Option<Product>,
) -> EnvironmentSkillLoadOutcome {
let mut outcome = EnvironmentSkillLoadOutcome::default();
let discovery =
discover_skills_under_root(file_system, root, SymlinkPolicy::FollowDirectories).await;
let discovery = match file_system
.walk(
root,
WalkOptions {
max_depth: MAX_SCAN_DEPTH,
max_directories: MAX_SKILLS_DIRS_PER_ROOT,
max_entries: MAX_SKILLS_ENTRIES_PER_ROOT,
},
/*sandbox*/ None,
)
.await
{
Ok(walk) => {
let mut warnings = walk
.errors
.into_iter()
.map(|error| {
format!(
"failed to scan skill path {}: {}",
error.path, error.message
)
})
.collect::<Vec<_>>();
if walk.truncated {
warnings.push(format!(
"skills scan reached its traversal limit (root: {root})"
));
}
SkillFileDiscovery {
skill_files: walk
.entries
.into_iter()
.filter(|entry| {
entry.kind == WalkEntryKind::File
&& entry.path.basename().as_deref() == Some(SKILLS_FILENAME)
})
.map(|entry| entry.path)
.collect(),
warnings,
}
}
Err(error) if error.kind() == io::ErrorKind::NotFound => SkillFileDiscovery {
skill_files: Vec::new(),
warnings: Vec::new(),
},
Err(error) => SkillFileDiscovery {
skill_files: Vec::new(),
warnings: vec![format!("failed to walk skills root {root}: {error:#}")],
},
};
outcome.warnings.extend(discovery.warnings);
for path in discovery.skill_files {
match EnvironmentSkillMetadata::parse(file_system, &path).await {