Describe the bug
gh skill preview and gh skill install do not discover a repository whose skill lives directly at the repository root as SKILL.md.
The error message says */SKILL.md is an expected convention, and the source code appears to have a root convention for this case, but a public repo with exactly that layout still fails with no skills found.
Concrete public example: https://github.com/blader/humanizer
That repo is a single-skill repository for Claude Code/OpenCode:
blader/humanizer
├── AGENTS.md
├── LICENSE
├── README.md
└── SKILL.md
The root SKILL.md has valid frontmatter, including name: humanizer, description, and license: MIT.
Affected version
gh version 2.93.0 (2026-05-27)
https://github.com/cli/cli/releases/tag/v2.93.0
Steps to reproduce the behavior
-
Confirm the remote repo has a root-level SKILL.md:
gh api repos/blader/humanizer/git/trees/main?recursive=true \
--jq '.tree[] | select(.path == "SKILL.md") | {path,type,sha,size}'
Output:
{"path":"SKILL.md","sha":"abf342f943861a1649c116eb0c66cfc07db0ab5d","size":32014,"type":"blob"}
-
Try to preview the repo:
gh skill preview blader/humanizer
-
Try to install the repo:
gh skill install blader/humanizer
-
Try passing the root file explicitly:
gh skill preview blader/humanizer SKILL.md
gh skill install blader/humanizer SKILL.md --dir /tmp/humanizer-skill
Expected vs actual behavior
Expected:
gh skill preview blader/humanizer discovers and previews the root-level skill.
gh skill install blader/humanizer humanizer or an interactive selection from gh skill install blader/humanizer can install it.
- If exact paths are supported,
SKILL.md should resolve to the root skill as well.
Actual:
$ gh skill preview blader/humanizer
no skills found in blader/humanizer
Expected skills in skills/*/SKILL.md, skills/{scope}/*/SKILL.md,
{prefix}/skills/*/SKILL.md, {prefix}/skills/{scope}/*/SKILL.md,
*/SKILL.md, or plugins/*/skills/*/SKILL.md
This repository may be a curated list rather than a skills publisher
$ gh skill install blader/humanizer
Using ref main (a2ace14a)
no skills found in blader/humanizer
Expected skills in skills/*/SKILL.md, skills/{scope}/*/SKILL.md,
{prefix}/skills/*/SKILL.md, {prefix}/skills/{scope}/*/SKILL.md,
*/SKILL.md, or plugins/*/skills/*/SKILL.md
This repository may be a curated list rather than a skills publisher
Passing the root path explicitly also does not work:
$ gh skill preview blader/humanizer SKILL.md
no skills found in blader/humanizer
Expected skills in skills/*/SKILL.md, skills/{scope}/*/SKILL.md,
{prefix}/skills/*/SKILL.md, {prefix}/skills/{scope}/*/SKILL.md,
*/SKILL.md, or plugins/*/skills/*/SKILL.md
This repository may be a curated list rather than a skills publisher
$ gh skill install blader/humanizer SKILL.md --dir /tmp/humanizer-skill
Using ref main (a2ace14a)
skill directory "SKILL.md" not found in blader/humanizer
Logs
$ GH_DEBUG=api gh skill preview blader/humanizer
* Request to https://api.github.com/repos/blader/humanizer/git/ref/heads/main
* Request to https://api.github.com/repos/blader/humanizer
* Request to https://api.github.com/repos/blader/humanizer/git/trees/a2ace14a4a5d28f88d6e178b1081e8db91fa8371?recursive=true
no skills found in blader/humanizer
Expected skills in skills/*/SKILL.md, skills/{scope}/*/SKILL.md,
{prefix}/skills/*/SKILL.md, {prefix}/skills/{scope}/*/SKILL.md,
*/SKILL.md, or plugins/*/skills/*/SKILL.md
This repository may be a curated list rather than a skills publisher
Root cause
This looks like an off-by-one/path interpretation bug in internal/skills/discovery/discovery.go.
In matchSkillConventions, the code derives the skill name from path.Base(path.Dir(entry.Path)):
dir := path.Dir(entry.Path)
parentDir := path.Dir(dir)
skillName := path.Base(dir)
if !validateName(skillName) {
return nil
}
For a root-level SKILL.md, entry.Path == "SKILL.md", so:
dir == "."
parentDir == "."
skillName == "."
The early validateName(skillName) check rejects ".", so execution never reaches the later root convention branch:
if parentDir == "." && skillName != "skills" && skillName != "plugins" && !strings.HasPrefix(skillName, ".") {
return &skillMatch{entry: entry, name: skillName, skillDir: dir, convention: "root"}
}
Even if it did, the current derivation would name the skill "." instead of using the repository name or the name: from frontmatter.
There is a related closed issue for gh skill publish with root-level SKILL.md (#13284), but I could not find an existing issue for remote preview/install discovery failing on this same single-skill repository layout. #13372 and #13495 cover nested/non-conventional skill paths, but this report is about the */SKILL.md convention already shown in the error message.
Suggested fix
Handle entry.Path == "SKILL.md" before validating skillName derived from the containing directory.
For a root-level single-skill repository, discovery probably needs to derive the skill name from the SKILL.md frontmatter (name: humanizer) or from the repository name, not from path.Base("."). DiscoverSkillByPath may also need a special case for SKILL.md, since it currently treats the argument as a skill directory path and looks for a directory literally named SKILL.md.
This would make remote discovery consistent with local install behavior: gh skill install <local-path> <name> --from-local already has a test named single skill directory (SKILL.md at root) and succeeds for a local directory containing only a root SKILL.md.
Describe the bug
gh skill previewandgh skill installdo not discover a repository whose skill lives directly at the repository root asSKILL.md.The error message says
*/SKILL.mdis an expected convention, and the source code appears to have arootconvention for this case, but a public repo with exactly that layout still fails withno skills found.Concrete public example: https://github.com/blader/humanizer
That repo is a single-skill repository for Claude Code/OpenCode:
The root
SKILL.mdhas valid frontmatter, includingname: humanizer,description, andlicense: MIT.Affected version
Steps to reproduce the behavior
Confirm the remote repo has a root-level
SKILL.md:Output:
{"path":"SKILL.md","sha":"abf342f943861a1649c116eb0c66cfc07db0ab5d","size":32014,"type":"blob"}Try to preview the repo:
Try to install the repo:
Try passing the root file explicitly:
Expected vs actual behavior
Expected:
gh skill preview blader/humanizerdiscovers and previews the root-level skill.gh skill install blader/humanizer humanizeror an interactive selection fromgh skill install blader/humanizercan install it.SKILL.mdshould resolve to the root skill as well.Actual:
Passing the root path explicitly also does not work:
Logs
Root cause
This looks like an off-by-one/path interpretation bug in
internal/skills/discovery/discovery.go.In
matchSkillConventions, the code derives the skill name frompath.Base(path.Dir(entry.Path)):For a root-level
SKILL.md,entry.Path == "SKILL.md", so:The early
validateName(skillName)check rejects".", so execution never reaches the later root convention branch:Even if it did, the current derivation would name the skill
"."instead of using the repository name or thename:from frontmatter.There is a related closed issue for
gh skill publishwith root-levelSKILL.md(#13284), but I could not find an existing issue for remotepreview/installdiscovery failing on this same single-skill repository layout. #13372 and #13495 cover nested/non-conventional skill paths, but this report is about the*/SKILL.mdconvention already shown in the error message.Suggested fix
Handle
entry.Path == "SKILL.md"before validatingskillNamederived from the containing directory.For a root-level single-skill repository, discovery probably needs to derive the skill name from the
SKILL.mdfrontmatter (name: humanizer) or from the repository name, not frompath.Base(".").DiscoverSkillByPathmay also need a special case forSKILL.md, since it currently treats the argument as a skill directory path and looks for a directory literally namedSKILL.md.This would make remote discovery consistent with local install behavior:
gh skill install <local-path> <name> --from-localalready has a test namedsingle skill directory (SKILL.md at root)and succeeds for a local directory containing only a rootSKILL.md.