Skip to content

gh skill preview/install fail to discover a root-level SKILL.md skill repository #13552

@FunJim

Description

@FunJim

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

  1. 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"}
  2. Try to preview the repo:

    gh skill preview blader/humanizer
  3. Try to install the repo:

    gh skill install blader/humanizer
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementa request to improve CLIgh-skillrelating to the gh skill command

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions