-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
fix(skills): detect nested SKILL.md in unmanaged scan #1917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -880,25 +880,37 @@ impl SkillService { | |
| let mut unmanaged: HashMap<String, UnmanagedSkill> = HashMap::new(); | ||
|
|
||
| for (scan_dir, label) in &scan_sources { | ||
| let entries = match fs::read_dir(scan_dir) { | ||
| Ok(e) => e, | ||
| Err(_) => continue, | ||
| let skill_dirs = match Self::scan_skills_in_dir(scan_dir) { | ||
| Ok(dirs) => dirs, | ||
| Err(err) => { | ||
| log::warn!("Failed to scan skills in {}: {err}", scan_dir.display()); | ||
| continue; | ||
| } | ||
| }; | ||
| for entry in entries.flatten() { | ||
| let path = entry.path(); | ||
| if !path.is_dir() { | ||
|
|
||
| for skill_dir in skill_dirs { | ||
| let rel = match skill_dir.strip_prefix(scan_dir) { | ||
| Ok(path) => path, | ||
| Err(_) => continue, | ||
| }; | ||
| if rel.components().next().is_none() { | ||
| continue; | ||
| } | ||
| let dir_name = entry.file_name().to_string_lossy().to_string(); | ||
| let dir_name = rel.to_string_lossy().to_string(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Using Useful? React with 👍 / 👎. |
||
| if dir_name.starts_with('.') || managed_dirs.contains(&dir_name) { | ||
| continue; | ||
| } | ||
|
|
||
| let skill_md = path.join("SKILL.md"); | ||
| let skill_md = skill_dir.join("SKILL.md"); | ||
| if !skill_md.exists() { | ||
| continue; | ||
| } | ||
| let (name, description) = Self::read_skill_name_desc(&skill_md, &dir_name); | ||
|
|
||
| let fallback_name = skill_dir | ||
| .file_name() | ||
| .map(|name| name.to_string_lossy().to_string()) | ||
| .unwrap_or_else(|| dir_name.clone()); | ||
| let (name, description) = Self::read_skill_name_desc(&skill_md, &fallback_name); | ||
|
|
||
| unmanaged | ||
| .entry(dir_name.clone()) | ||
|
|
@@ -908,7 +920,7 @@ impl SkillService { | |
| name, | ||
| description, | ||
| found_in: vec![label.clone()], | ||
| path: path.display().to_string(), | ||
| path: skill_dir.display().to_string(), | ||
| }); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scan_unmanagednow calls the recursive scanner for user skill roots, but the recursion follows symlinked directories (path.is_dir()) without any visited-path guard. A symlink cycle inside a skills folder (for exampleloop -> ..) will recurse forever and can hang or stack-overflow unmanaged scanning. Since this path is now exercised on normal app directories, the scan should skip symlink dirs or track canonicalized visited directories.Useful? React with 👍 / 👎.