Recognize .claude-plugin/plugin.json for external plugin submissions 🤖🤖🤖 - #2524
Open
conorbronsdon wants to merge 2 commits into
Open
Recognize .claude-plugin/plugin.json for external plugin submissions 🤖🤖🤖#2524conorbronsdon wants to merge 2 commits into
conorbronsdon wants to merge 2 commits into
Conversation
External plugin intake and quality gates resolve a submission's manifest from a fixed list of three locations: .github/plugin/plugin.json, .plugin/plugin.json, and plugin.json. A plugin whose manifest sits at .claude-plugin/plugin.json is reported as "No plugin.json found" and fails the version match gate before a maintainer sees it. The Copilot CLI already searches that location. The manifest resolver in the shipped native runtime (@github/copilot-linux-x64) carries the candidate directory set root, .plugin, .github/plugin, .claude-plugin, and the equivalent set for marketplace.json. So the intake list is a subset of what the CLI itself installs from, and the gap rejects plugins that would install correctly. Fifteen of the 37 entries already in plugins/external.json ship a .claude-plugin/plugin.json. For microsoft/skills-for-copilot-studio it is the only manifest in the repository, so its manifest is unresolvable under the current list. Add the location as a fourth candidate in all three copies of the list, ordered last so a repository shipping any of the original three keeps resolving to that one. netlify/context-and-tools is the case that makes ordering matter: it ships name=netlify version=1.1.0 at .github/plugin/plugin.json and name=netlify-skills version=1.2.0 at .claude-plugin/plugin.json, and external.json pins 1.1.0. Two secondary effects, both widening to match how the other three locations are already treated. Intake now resolves canvas metadata from the new location, and buildVallyLintArgs now lints the directories named in a .claude-plugin manifest's skills array rather than falling back to the whole plugin root. Tests cover the new location, the unchanged failure when no manifest exists in any location, and the precedence of .github/plugin/plugin.json when both are present. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
🔴 Contributor Reputation Check: HIGH risk
Maintainers: please review this contributor before merging. |
The comment said `.claude-plugin/plugin.json` is "the Claude Code plugin spec location that CONTRIBUTING.md points external submitters at." CONTRIBUTING.md does not mention `.claude-plugin` at all -- `grep -c` returns 0. It points submitters at `.github/plugin/plugin.json` (lines 145 and 175); what it borrows from the Claude Code spec is the field vocabulary (`agents`, `commands`, `skills`), not the directory. The actual justification is the one in the PR description and it did not travel into the code: the Copilot CLI's own manifest resolver searches this directory, so a plugin the CLI can install should resolve here too. Stating that instead, since it is the claim the change rests on and it is the one that survives a maintainer checking it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request Checklist
npm startand verified thatREADME.mdis up to date.mainbranch for this pull request.Two boxes are unchecked because they do not apply: this is a fix to the external plugin intake scripts in
eng/, not a new resource file.Description
External plugin intake resolves a submission's manifest from a fixed list of three locations:
.github/plugin/plugin.json,.plugin/plugin.json, andplugin.json. A plugin whose manifest sits at.claude-plugin/plugin.jsonfails the version match gate withNo plugin.json found, and the submitter getsrequires-submitter-fixesbefore a maintainer looks at it.This adds
.claude-plugin/plugin.jsonas a fourth candidate.Why this location
The Copilot CLI already searches it. The manifest resolver in the shipped native runtime carries the candidate directory set root,
.plugin,.github/plugin,.claude-plugin, plus the matching set formarketplace.json:The intake list is a subset of what the CLI installs from, so the gate currently rejects plugins that would install correctly.
This is not hypothetical for the existing catalog. Probing all 37 entries in
plugins/external.jsonat their pinned ref/sha, 15 ship a.claude-plugin/plugin.json. Formicrosoft/skills-for-copilot-studioit is the only manifest in the repository, so its manifest is unresolvable under the current list.Why last in the list
Resolution is first-match-wins, so appending cannot change any submission that resolves today.
netlify/context-and-toolsis the entry that makes this concrete. It ships two manifests with different names and versions:.github/plugin/plugin.jsonnetlify.claude-plugin/plugin.jsonnetlify-skillsexternal.jsonpins 1.1.0. Ordering.claude-pluginfirst would flip that listed entry from pass to fail; ordering it last keeps it passing. There is a test for exactly this.Scope
The list exists in three places, which I updated together:
external-plugin-quality-gates.mjsPLUGIN_JSON_CANDIDATES, manifest resolution for the quality gatesexternal-plugin-intake.mjsexternal-plugin-validation.mjsTwo secondary effects, both widening
.claude-pluginto match how the other three locations are already handled: intake resolves canvas metadata from the new location, andbuildVallyLintArgslints the directories named in a.claude-pluginmanifest'sskillsarray instead of falling back to the whole plugin root.I could not run the install smoke test, because it shells out to the
copilotCLI and that is not available in my environment. ReadingrunInstallSmokeGate, its post-install assertion calls the samefindPluginJson, so it widens consistently with the resolver.Type of Contribution
eng/external plugin intake and quality gate scripts.Additional Notes
Verification
Reproduction of the bug against a real repository, using
runVersionMatchGatefrom this repo unmodified:Same call with this branch applied:
Test suites:
The three new tests were mutation-checked by reverting only the source change and re-running:
Test 13 covers the fix. Test 14 is a no-regression guard whose fix-sensitive assertion is the error message enumerating all four locations. Test 15 passes on both, as a guard should. Moving
.claude-pluginto the front of the list fails it, which is what pins the ordering.Repository toolchain:
All four changed files are LF, verified with
git cat-file blob <sha> | tr -dc '\r' | wc -creturning 0.Follow-up worth a separate issue
The gate's manifest precedence is not verified to match the CLI's. For
netlifythe gate validatesnetlify1.1.0 from.github/plugin/, while the CLI may loadnetlify-skills1.2.0 from.claude-plugin/. That divergence predates this PR, since it is already possible between.pluginand the repository root, so I left it alone rather than widen the change.I am happy to drop the
external-plugin-intake.mjsandexternal-plugin-validation.mjschanges if you would rather keep this to the quality gate alone, though the in-repo comments ask for the three lists to be kept in sync.🤖 Generated with Claude Code