From 27d859e815fe285bff1a877dc53005e56f1a2593 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Tue, 23 Jun 2026 16:35:22 +0100 Subject: [PATCH 1/2] Fix SearchDirectoriesForSkills to stop recursing after finding SKILL.md When a directory contains SKILL.md, subdirectories are part of that skill and should not be treated as independent skill roots. Add a return after adding the directory to results to prevent incorrect recursion. Also adds a regression test verifying nested SKILL.md files are not discovered as separate skills. Fixes microsoft/agent-framework#6683 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Skills/File/AgentFileSkillsSource.cs | 3 +++ .../AgentSkills/FileAgentSkillLoaderTests.cs | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs b/dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs index 54a5dec10cc..430977bf276 100644 --- a/dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs +++ b/dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs @@ -162,7 +162,10 @@ private static void SearchDirectoriesForSkills(string directory, List re string skillFilePath = Path.Combine(directory, SkillFileName); if (File.Exists(skillFilePath)) { + // Once a SKILL.md is found, this directory is the skill root. + // Subdirectories are part of this skill and should not be treated as independent skill roots. results.Add(Path.GetFullPath(directory)); + return; } if (currentDepth >= MaxSkillDirectorySearchDepth) diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs index 6eb561c5b69..9e1e76b3c53 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs @@ -1199,4 +1199,26 @@ public async Task GetSkillsAsync_SymlinkedFileInRealDirectory_SkipsSymlinkedFile Assert.Equal("references/legit.md", skill.GetTestResources()![0].Name); } #endif + + [Fact] + public async Task GetSkillsAsync_NestedSkillMd_DoesNotTreatSubdirectoryAsIndependentSkillAsync() + { + // Arrange — parent has SKILL.md; subdirectory also has SKILL.md. + // Only the parent should be discovered as a skill root. + string parentSkillDir = this.CreateSkillDirectory("parent-skill", "Parent skill", "Parent body."); + string childDir = Path.Combine(parentSkillDir, "child"); + Directory.CreateDirectory(childDir); + File.WriteAllText( + Path.Combine(childDir, "SKILL.md"), + "---\nname: child-skill\ndescription: Child skill\n---\nChild body."); + + var source = new AgentFileSkillsSource(this._testRoot, s_noOpExecutor); + + // Act + var skills = await source.GetSkillsAsync(); + + // Assert — only the parent skill is discovered; the nested child is not an independent skill + Assert.Single(skills); + Assert.Equal("parent-skill", skills[0].Frontmatter.Name); + } } From af0d5c719de5bd44b5fe10727e09fa42d7a3003d Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Tue, 23 Jun 2026 16:46:14 +0100 Subject: [PATCH 2/2] Fix test: use matching directory name so nested SKILL.md would pass validation The child skill's frontmatter name must match its directory name, otherwise it gets rejected by validation regardless of the recursion fix. This ensures the test actually validates the stop-recursing behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AgentSkills/FileAgentSkillLoaderTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs index 9e1e76b3c53..b001b46504e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs @@ -1203,14 +1203,15 @@ public async Task GetSkillsAsync_SymlinkedFileInRealDirectory_SkipsSymlinkedFile [Fact] public async Task GetSkillsAsync_NestedSkillMd_DoesNotTreatSubdirectoryAsIndependentSkillAsync() { - // Arrange — parent has SKILL.md; subdirectory also has SKILL.md. + // Arrange — parent has SKILL.md; subdirectory also has SKILL.md with a name + // matching its directory so it would pass validation if discovered. // Only the parent should be discovered as a skill root. string parentSkillDir = this.CreateSkillDirectory("parent-skill", "Parent skill", "Parent body."); string childDir = Path.Combine(parentSkillDir, "child"); Directory.CreateDirectory(childDir); File.WriteAllText( Path.Combine(childDir, "SKILL.md"), - "---\nname: child-skill\ndescription: Child skill\n---\nChild body."); + "---\nname: child\ndescription: Child skill\n---\nChild body."); var source = new AgentFileSkillsSource(this._testRoot, s_noOpExecutor);