@@ -353,6 +353,44 @@ description: A flat global agents skill
353353 assert .True (t , foundFlat , "Expected to find flat-skill from ~/.agents/skills/flat-skill" )
354354}
355355
356+ func TestLoadSkillsFromDir_RecursiveSymlinkCycle (t * testing.T ) {
357+ tmpDir := t .TempDir ()
358+
359+ // Create a skill in a subdirectory.
360+ skillDir := filepath .Join (tmpDir , "real-skill" )
361+ require .NoError (t , os .MkdirAll (skillDir , 0o755 ))
362+
363+ skillContent := `---
364+ name: real-skill
365+ description: A real skill
366+ ---
367+
368+ # Real Skill
369+ `
370+ require .NoError (t , os .WriteFile (filepath .Join (skillDir , "SKILL.md" ), []byte (skillContent ), 0o644 ))
371+
372+ // Create a symlink cycle: tmpDir/real-skill/link -> tmpDir
373+ require .NoError (t , os .Symlink (tmpDir , filepath .Join (skillDir , "link" )))
374+
375+ // loadSkillsRecursive must return without looping forever.
376+ skills := loadSkillsFromDir (tmpDir , true )
377+
378+ require .Len (t , skills , 1 )
379+ assert .Equal (t , "real-skill" , skills [0 ].Name )
380+ assert .Equal (t , "A real skill" , skills [0 ].Description )
381+ }
382+
383+ func TestLoadSkillsFromDir_RecursiveSymlinkSelfReference (t * testing.T ) {
384+ tmpDir := t .TempDir ()
385+
386+ // Create a directory that symlinks to itself.
387+ require .NoError (t , os .Symlink (tmpDir , filepath .Join (tmpDir , "self" )))
388+
389+ // Must not loop forever.
390+ skills := loadSkillsFromDir (tmpDir , true )
391+ assert .Empty (t , skills )
392+ }
393+
356394func TestLoad_AgentsSkillsProjectFromNestedDir (t * testing.T ) {
357395 // Create a fake git repo with .agents/skills at the root
358396 tmpRepo := t .TempDir ()
0 commit comments