Add --allow-hidden-dirs flag to gh skill install#13213
Conversation
3352e80 to
f5505ac
Compare
There was a problem hiding this comment.
Pull request overview
Adds an opt-in --allow-hidden-dirs flag to gh skill install to discover skills stored under dot-prefixed directories (e.g. .claude/skills/, .agents/skills/), including improved messaging and warnings about potential attribution concerns.
Changes:
- Introduces hidden-directory discovery conventions and options-based discovery APIs (remote + local).
- Updates install flow to wire
--allow-hidden-dirsinto discovery and warn when hidden-dir skills are included. - Adds tests for hidden-dir matching and discovery option behavior.
Show a summary per file
| File | Description |
|---|---|
pkg/cmd/skills/install/install.go |
Adds the --allow-hidden-dirs flag, uses options-based discovery, and prints a hidden-dir warning when applicable. |
internal/skills/discovery/discovery.go |
Implements hidden-dir conventions, discovery options, and hidden-skill counting for improved “no skills found” messaging. |
internal/skills/discovery/discovery_test.go |
Adds unit tests for hidden-dir matching, discovery options behavior, and display name/warning helpers. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 3/3 changed files
- Comments generated: 3
| msg := fmt.Sprintf("no skills found in %s/%s\n"+ | ||
| " Expected skills in skills/*/SKILL.md, skills/{scope}/*/SKILL.md,\n"+ | ||
| " */SKILL.md, or plugins/*/skills/*/SKILL.md\n"+ | ||
| " This repository may be a curated list rather than a skills publisher", | ||
| owner, repo) | ||
| if hiddenCount > 0 { | ||
| msg += fmt.Sprintf("\n Found %d skill(s) in hidden directories - use --allow-hidden-dirs to include them", hiddenCount) | ||
| } | ||
| return nil, errors.New(msg) |
There was a problem hiding this comment.
The discovery package’s “no skills found” error message hard-codes an install-command flag ("use --allow-hidden-dirs"). DiscoverSkills is also used by other commands (e.g. pkg/cmd/skills/preview/preview.go:154, pkg/cmd/skills/update/update.go:283), so they can end up emitting guidance for a flag they don’t support. Consider returning a typed error that includes HiddenCount, and let each command decide how to format user-facing guidance (or make the hint configurable via DiscoverOptions).
There was a problem hiding this comment.
I agree with this - let's emit a typed error and consume it higher up the stack in the gh skill install command logic.
| msg := fmt.Sprintf("no skills found in %s\n"+ | ||
| " Expected SKILL.md in the directory, or skills in skills/*/SKILL.md,\n"+ | ||
| " skills/{scope}/*/SKILL.md, */SKILL.md, or plugins/*/skills/*/SKILL.md", | ||
| dir) | ||
| if hiddenCount > 0 { | ||
| msg += fmt.Sprintf("\n Found %d skill(s) in hidden directories - use --allow-hidden-dirs to include them", hiddenCount) | ||
| } | ||
| return nil, errors.New(msg) |
There was a problem hiding this comment.
Same issue for local discovery: the returned error message includes "use --allow-hidden-dirs", but DiscoverLocalSkills is consumed by other commands like gh skill publish (pkg/cmd/skills/publish/publish.go:188). This couples a shared library error to a specific CLI flag. Prefer a typed error (including hidden-skill count) and have the CLI layer append flag-specific guidance only where supported.
There was a problem hiding this comment.
Yeah, Copilot's right. Mixing display/business logic ties our hands like this.
How about returning both normal and hidden skills from this function, and then at the caller side we decide on the error message (based on one being empty and the other non-empty)?
babakks
left a comment
There was a problem hiding this comment.
LGTM, except for missing tests. I approve in case you're planning to do a follow up for tests.
| msg := fmt.Sprintf("no skills found in %s\n"+ | ||
| " Expected SKILL.md in the directory, or skills in skills/*/SKILL.md,\n"+ | ||
| " skills/{scope}/*/SKILL.md, */SKILL.md, or plugins/*/skills/*/SKILL.md", | ||
| dir) | ||
| if hiddenCount > 0 { | ||
| msg += fmt.Sprintf("\n Found %d skill(s) in hidden directories - use --allow-hidden-dirs to include them", hiddenCount) | ||
| } | ||
| return nil, errors.New(msg) |
There was a problem hiding this comment.
Yeah, Copilot's right. Mixing display/business logic ties our hands like this.
How about returning both normal and hidden skills from this function, and then at the caller side we decide on the error message (based on one being empty and the other non-empty)?
| { | ||
| name: "hidden dirs excluded by default", | ||
| opts: DiscoverOptions{}, | ||
| tree: hiddenDirTree, | ||
| wantErr: "Found 2 skill(s) in hidden directories", | ||
| }, |
There was a problem hiding this comment.
Let's also have an edge-case test for when there' no skill at all.
| // printHiddenDirWarning warns that skills found in hidden directories may be | ||
| // installed copies from another publisher rather than original publications. | ||
| func printHiddenDirWarning(w io.Writer, cs *iostreams.ColorScheme) { | ||
| fmt.Fprintf(w, "%s Skills in hidden directories (e.g. .claude/, .agents/) may be installed copies\n"+ | ||
| " from another publisher. Verify the skill's origin and check for a canonical source.\n", cs.WarningIcon()) | ||
| } |
There was a problem hiding this comment.
💭 Could consider inlining this
💅 Should be a heredoc.Docf string for readability
|
One comment on the provenance issue for skills in hidden directories (which I mentioned in community/community#192851) would be to inspect the metadata of the found skill and preserve the upstream location metadata if it already existed. This would retain the link to the original skill publisher (for attribution) but probably needs a warning or notice to the user that they aren't actually installing from the specified location. |
Move --allow-hidden-dirs filtering logic from the discovery package to the install command, addressing review feedback. Discovery functions now always return all skills (including hidden-dir), and callers decide how to handle them. Changes: - DiscoverSkillsWithOptions/DiscoverLocalSkillsWithOptions always return hidden-dir skills; callers filter using IsHiddenDirConvention() - DiscoverSkills/DiscoverLocalSkills (convenience wrappers) auto-filter hidden-dir skills for backward compatibility with preview/update/publish - Remove --allow-hidden-dirs reference from discovery error messages - Add filterHiddenDirSkills in install.go with caller-side flag logic - Inline warning using heredoc.Docf, remove printHiddenDirWarning - Add inline comments in matchHiddenDirConventions (babakks nitpicks) - Add non-hidden-namespaced dir and no-skills-at-all test cases - Add --allow-hidden-dirs tests in TestNewCmdInstall, TestInstallRun, and TestRunLocalInstall Co-authored-by: Copilot <[email protected]>
f5505ac to
eaa0185
Compare
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [cli/cli](https://github.com/cli/cli) | minor | `v2.90.0` → `v2.92.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>cli/cli (cli/cli)</summary> ### [`v2.92.0`](https://github.com/cli/cli/releases/tag/v2.92.0): GitHub CLI 2.92.0 [Compare Source](cli/cli@v2.91.0...v2.92.0) #### Support GitHub Enterprise Cloud (GHEC) in `skill` commandset Now `gh skill` subcommands (`install`, `preview`, `publish`, `search`, `update`) are able to work with [GHEC](https://docs.github.com/en/enterprise-cloud@latest/admin/overview/about-github-enterprise-cloud) hosts with data residency. #### Add `--allow-hidden-dirs` flag to `skill preview` Following the addition of `--allow-hidden-dirs` to `skill install` in the previous release, now the flag is also supported in `skill preview`, allowing users to preview skills located in hidden (dot-prefixed) directories such as `.claude/skills/`, `.agents/skills/`, and `.github/skills/`. #### What's Changed ##### ✨ Features - feat(skills): add --allow-hidden-dirs flag to preview command by [@​SamMorrowDrums](https://github.com/SamMorrowDrums) in [#​13265](cli/cli#13265) - feat(skills): support GHEC with data residency hosts by [@​SamMorrowDrums](https://github.com/SamMorrowDrums) in [#​13264](cli/cli#13264) ##### 🐛 Fixes - Fix SetSampleRate not updating sample\_rate dimension by [@​williammartin](https://github.com/williammartin) in [#​13259](cli/cli#13259) - Fix log terminal injection by [@​williammartin](https://github.com/williammartin) in [#​13272](cli/cli#13272) - Add "Resource not accessible" to ProjectsV2IgnorableError by [@​maxbeizer](https://github.com/maxbeizer) in [#​13281](cli/cli#13281) ##### 📚 Docs & Chores - fix: using variable interpolation \`${{ in deployment.yml... by [@​orbisai0security](https://github.com/orbisai0security) in [#​13258](cli/cli#13258) - docs: correct typo in Linux Homebrew copy by [@​cassidyjames](https://github.com/cassidyjames) in [#​13273](cli/cli#13273) - Install skills flat by Name, not namespaced InstallName by [@​SamMorrowDrums](https://github.com/SamMorrowDrums) in [#​13266](cli/cli#13266) - chore: fix zsh completion on debian by [@​babakks](https://github.com/babakks) in [#​13274](cli/cli#13274) - Add trust disclaimer to extension help text by [@​travellertales](https://github.com/travellertales) in [#​13296](cli/cli#13296) - Bump Go to 1.26.2 by [@​github-actions](https://github.com/github-actions)\[bot] in [#​13301](cli/cli#13301) #####Dependencies - chore(deps): bump github.com/mattn/go-isatty from 0.0.20 to 0.0.21 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​13161](cli/cli#13161) - chore(deps): bump github.com/google/go-containerregistry from 0.21.4 to 0.21.5 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​13162](cli/cli#13162) - chore(deps): bump charm.land/lipgloss/v2 from 2.0.2 to 2.0.3 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​13163](cli/cli#13163) - chore(deps): bump charm.land/bubbletea/v2 from 2.0.2 to 2.0.6 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​13206](cli/cli#13206) - chore(deps): bump github.com/gdamore/tcell/v2 from 2.13.8 to 2.13.9 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​13241](cli/cli#13241) - chore(deps): bump github.com/mattn/go-isatty from 0.0.21 to 0.0.22 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​13298](cli/cli#13298) #### New Contributors - [@​orbisai0security](https://github.com/orbisai0security) made their first contribution in [#​13258](cli/cli#13258) - [@​cassidyjames](https://github.com/cassidyjames) made their first contribution in [#​13273](cli/cli#13273) - [@​travellertales](https://github.com/travellertales) made their first contribution in [#​13296](cli/cli#13296) **Full Changelog**: <cli/cli@v2.91.0...v2.92.0> ### [`v2.91.0`](https://github.com/cli/cli/releases/tag/v2.91.0): GitHub CLI 2.91.0 [Compare Source](cli/cli@v2.90.0...v2.91.0) #### GitHub CLI now collects pseudonymous telemetry To better understand how features are used in practice, especially as agentic adoption grows, GitHub CLI now sends pseudonymous telemetry. See [Telemetry](https://cli.github.com/telemetry) for more details on what's collected, why, and how to opt out. #### Support more agents in `gh skill` Thanks to community feedback, `gh` now supports a large number of agent hosts. Run `gh skill install --help` for the list of available agents. #### Improve skill discovery `gh skill install` now adds the `--allow-hidden-dirs` flag to support discovering skills in hidden (dot-prefixed) directories such as `.claude/skills/`, `.agents/skills/`, and `.github/skills/`. #### Detect skills re-published from other sources GitHut CLI now detects if the skill to be installed is re-published from an upstream source and offers the option to install it from there. The `--upstream` flag is also added for non-interactive use cases. #### What's Changed ##### ✨ Features - Add support for installation in multiple agent hosts in `gh skills install` by [@​tommaso-moro](https://github.com/tommaso-moro) in [#​13209](cli/cli#13209) - Add --allow-hidden-dirs flag to gh skill install by [@​SamMorrowDrums](https://github.com/SamMorrowDrums) in [#​13213](cli/cli#13213) - Make skill discovery less strict: support nested `skills/` directories by [@​SamMorrowDrums](https://github.com/SamMorrowDrums) in [#​13235](cli/cli#13235) - feat(skills): detect re-published skills and offer upstream install by [@​SamMorrowDrums](https://github.com/SamMorrowDrums) in [#​13236](cli/cli#13236) ##### 🐛 Fixes - Fix `skills publish --fix` to not publish by [@​SamMorrowDrums](https://github.com/SamMorrowDrums) in [#​13237](cli/cli#13237) - fix(skills): match skills by install name in preview command by [@​SamMorrowDrums](https://github.com/SamMorrowDrums) in [#​13249](cli/cli#13249) ##### 📚 Docs & Chores - Remove misleading text by [@​tommaso-moro](https://github.com/tommaso-moro) in [#​13203](cli/cli#13203) - Add sampled command telemetry by [@​williammartin](https://github.com/williammartin) in [#​13191](cli/cli#13191) - Do not send telemetry for aliases by [@​williammartin](https://github.com/williammartin) in [#​13192](cli/cli#13192) - Add skills specific telemetry by [@​williammartin](https://github.com/williammartin) in [#​13204](cli/cli#13204) - Record CI context in telemetry by [@​williammartin](https://github.com/williammartin) in [#​13210](cli/cli#13210) - Record official extension telemetry by [@​williammartin](https://github.com/williammartin) in [#​13205](cli/cli#13205) - Add telemetry command by [@​williammartin](https://github.com/williammartin) in [#​13253](cli/cli#13253) - Log when there is no telemetry by [@​williammartin](https://github.com/williammartin) in [#​13255](cli/cli#13255) - docs(skills): add gh and gh-skill agent skills by [@​BagToad](https://github.com/BagToad) in [#​13244](cli/cli#13244) - Enable telemetry without env var by [@​williammartin](https://github.com/williammartin) in [#​13254](cli/cli#13254) **Full Changelog**: <cli/cli@v2.90.0...v2.91.0> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjkuNCIsInVwZGF0ZWRJblZlciI6IjQzLjE2OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWlub3IiXX0=-->
Description
Adds
--allow-hidden-dirsflag togh skill installto support discovering skills in hidden (dot-prefixed) directories such as.claude/skills/,.agents/skills/, and.github/skills/.Motivation
Some repositories publish skills exclusively under hidden directories (e.g.
.claude/skills/). The current discovery logic only looks in standard conventions (skills/,plugins/, root-level*/), so these repos appear empty togh skill install.We want to be less strict about where skills can live, but we do not want to fall back to pure recursive parsing of arbitrary directories. Hidden-dir discovery uses the same structured conventions as standard discovery, just rooted under a dot-prefixed host directory:
.{host}/skills/*/SKILL.md(hidden-dir).{host}/skills/{scope}/*/SKILL.md(hidden-dir-namespaced)This keeps discovery predictable and convention-based while supporting real-world repos.
Why this is opt-in
Hidden directories are typically installation targets (where consumers store skills) rather than publication roots (where publishers share skills). Skills found in these directories may be committed copies from another publisher, making attribution unclear. Keeping this behind a flag:
Architecture
Discovery layer returns all skills; callers decide filtering. Following review feedback, the discovery package no longer references CLI flags:
DiscoverSkillsWithOptions/DiscoverLocalSkillsWithOptionsalways return all skills (standard + hidden-dir)DiscoverSkills/DiscoverLocalSkills(convenience wrappers) auto-filter hidden-dir skills for backward compatibility withpreview,update, andpublishcommandsinstallcommand applies--allow-hidden-dirsflag logic viafilterHiddenDirSkills(), showing a clear error when hidden skills exist but the flag is not setUX
Error when hidden skills exist but flag is not set:
Warning when
--allow-hidden-dirsis set:Example
Works with both remote repos and
--from-local.Fixes #13197
Related: https://github.com/orgs/community/discussions/192851