fix(cli)!: emit scan output that passes claude plugin validate#20
Merged
Conversation
Plugin installs from this catalog were failing for three independent
reasons, all confirmed by running `claude plugin validate` against the
catalog manifest. This commit makes the detector emit shapes Claude
Code's parser actually accepts.
1. source field: switch from { source: "github", repo } to
{ source: "url", url: "https://github.com/<repo>.git" }.
The github discriminator routes through Claude Code's case 'github':
branch, which has an SSH-fallback bug (anthropics/claude-code#18001)
that breaks installs for users without SSH keys configured. The url
form is the canonical shape Anthropic's own claude-plugins-official
marketplace uses.
2. agents field: enumerate individual .md file paths instead of
emitting the directory path. Claude Code's schema accepts directory
paths for skills and commands but requires file paths for agents.
Our detector previously emitted `["./agents/"]` which validate
rejects with "plugins.N.agents: Invalid input". Now emits
`["./agents/foo.md", "./agents/bar.md", ...]`.
3. author normalization: when a source repo's manifest declares author
as a string, normalize to { name: "<string>" } object form. Claude
Code's schema requires the object form. Our valibot schema accepts
both for backward compat with hand-written entries, but the
synthesizer now emits the documented form.
All three issues surfaced together when running `claude plugin
validate /path/to/catalog` after merging the public-repo + marketplace-
rename PRs. Schema-only validation in our own valibot tests didn't
catch them because our schema was more permissive than Claude Code's.
Existing entries with old shapes still load (no breaking change to
the valibot schema), but they fail Claude Code's validate and won't
install. Regenerate with `ccpluginizer scan <owner/repo> --output
entries/<name>.json` to pick up the new shape.
Trade-off acknowledged: agent files are no longer auto-discovered at
install time. New agents added to a source repo require a re-scan +
catalog PR. Cost of Claude Code's schema choice; no way around it.
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.
Summary
Plugin installs from this catalog were failing for three independent reasons, all confirmed by running `claude plugin validate` against the catalog manifest. This PR makes the detector emit shapes Claude Code's parser actually accepts. After merge, `claude plugin validate /path/to/catalog` passes clean and installs work.
The three bugs and their fixes
1. source field: `github` → `url`
```diff
```
The `github` discriminator routes through Claude Code's `case 'github':` branch, which has the SSH-fallback bug from anthropics/claude-code#18001: on any HTTPS failure (including spurious ones in the install subprocess), the code falls back to `git@github.com:` and fails for users without SSH keys with a confusing `Permission denied (publickey)` error.
The `url` form is the canonical shape Anthropic's own claude-plugins-official marketplace uses for every GitHub-hosted plugin. Following that pattern.
2. agents field: directory path → enumerated file list
```diff
```
Claude Code's schema accepts directory paths for `skills` and `commands` but rejects them for `agents`. Running `claude plugin validate` on an entry with `agents: ["./agents/"]` produces:
```
✘ plugins.N.agents: Invalid input
```
The detector now walks the `agents/` folder and emits each `.md` file as an individual path.
Trade-off accepted: agents are no longer auto-discovered at install time. New agent files added to a source repo require a re-scan and a catalog PR. This is the unavoidable cost of Claude Code's schema choice for that field.
3. author normalization: string → object
```diff
```
Source repos that declare `author` as a string in their `.claude-plugin/manifest.json` get normalized to the documented object form. Claude Code rejects the string form:
```
✘ plugins.N.author: Invalid input: expected object, received string
```
Our valibot schema (`packages/cli/src/schemas/marketplaceEntry.ts:35`) still accepts the string form for backward compat with hand-written entries, but the synthesizer always emits object form going forward.
Why our own tests didn't catch this
The valibot schema we use for local validation is more permissive than Claude Code's parser. Our schema accepts:
The mismatch only surfaced when running `claude plugin validate` (Claude Code's own validator) against our marketplace, which the user prompted via the issue investigation. Worth considering whether to tighten our schema to match.
Test plan
Files changed