Skip to content

fix(cli)!: emit scan output that passes claude plugin validate#20

Merged
lifebugz merged 1 commit into
mainfrom
fix/scan-shape-for-claude-code-validation
May 10, 2026
Merged

fix(cli)!: emit scan output that passes claude plugin validate#20
lifebugz merged 1 commit into
mainfrom
fix/scan-shape-for-claude-code-validation

Conversation

@lifebugz
Copy link
Copy Markdown
Owner

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

  • "source": { "source": "github", "repo": "owner/repo" }

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

  • "agents": ["./agents/"]
  • "agents": ["./agents/foo.md", "./agents/bar.md", ...]
    ```

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

  • "author": "Open Circle"
  • "author": { "name": "Open Circle" }
    ```

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:

  • `source: github` or `source: url` (Claude Code's `github` branch is buggy but valid per schema)
  • `agents: ["./path/"]` directory form (Claude Code rejects)
  • `author: "string"` (Claude Code rejects)

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

  • `bun run --filter '@ccpluginizer/*' typecheck` — passes
  • `bun run --filter '@ccpluginizer/*' lint` — passes
  • `bun --filter='@ccpluginizer/*' test` — 59/59 (updated 3 tests for new shapes)
  • `bun run --filter '@ccpluginizer/*' build` — bundle builds clean
  • `bun scripts/build-marketplace.ts` — all 3 entries validate, marketplace rebuilds
  • `claude plugin validate .` from the catalog repo root → `✔ Validation passed` (was failing with `plugins.1.agents: Invalid input` before)
  • Post-merge verification: `claude plugin marketplace remove ccp-marketplace; claude plugin marketplace add lifebugz/ccpluginizer; claude plugin install elysiajs-skills@ccp-marketplace` should succeed

Files changed

  • `packages/cli/src/detector/conventions.ts` — agents folder enumeration
  • `packages/cli/src/detector/synthesize.ts` — `makeGithubSource` emits url form, author normalization
  • `packages/cli/tests/detector.test.ts` — assertion updated for enumerated agent files
  • `packages/cli/tests/synthesize.test.ts` — assertions updated for new source/agents/author shapes
  • `entries/*.json` (all 3) — regenerated with new shape
  • `.claude-plugin/marketplace.json` — rebuilt from updated entries
  • `.changeset/fix-scan-shape-for-claude-code-validation.md` — minor bump

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.
@lifebugz lifebugz merged commit 64a6060 into main May 10, 2026
2 checks passed
@lifebugz lifebugz deleted the fix/scan-shape-for-claude-code-validation branch May 10, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant