[BUG] Desktop plugin browser never lists plugins from a directory-source marketplace (listAvailablePlugins only scans ~/.claude/plugins/marketplaces/)

Status Fixed / completed
Reported on v2.1.280
Maintainer reply None cached
Activity 0 comments · opened Sep 23, 2026 · closed Sep 24, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code

What's Wrong?

The Claude desktop app's plugin browser (Customize → Plugins) never lists plugins from a marketplace that was added with a directory source (for example via extraKnownMarketplaces with "source": "directory" in a project's .claude/settings.json, or /plugin marketplace add ./path).

The CLI handles the same marketplace correctly:

  • claude plugin marketplace list shows it (Source: Directory (...))
  • claude plugin validate on the marketplace manifest passes
  • claude plugin list --json --available includes its plugins

The desktop app finds the marketplace but drops all of its plugins from the browse list.

Root cause (from the desktop app's NativeMarketplaceReader):

listMarketplaces reads known_marketplaces.json and correctly reports every registered marketplace. But listAvailablePlugins does not use each marketplace's installLocation from that file. Instead it enumerates the subdirectories of ~/.claude/plugins/marketplaces/ and reads <subdir>/.claude-plugin/marketplace.json from each one. A directory-source marketplace's installLocation is the source directory itself, so nothing ever exists under ~/.claude/plugins/marketplaces/ for it, and its plugins are never seen.

The relevant logic in the bundled app code, paraphrased:

// listAvailablePlugins
const names = await readdir(marketplacesDir, { withFileTypes: true })
  .filter(d => d.isDirectory() && !d.name.startsWith("."));
const manifests = await Promise.all(
  names.map(n => readJson(join(marketplacesDir, n, ".claude-plugin", "marketplace.json"))));
// ... plugins are collected only from these manifests

A symlink/junction under ~/.claude/plugins/marketplaces/ does not work around it, because the readdir filter keeps only real directories.

Once a plugin from the directory marketplace is installed via the CLI, the desktop app does list it (the installed-plugins branch of the same function reads installed_plugins.json), and its skills render with descriptions. So only discovery/browsing is broken, not installed-plugin display.

What Should Happen?

listAvailablePlugins should resolve each marketplace through its installLocation in known_marketplaces.json (the same way the CLI does), so directory- and file-source marketplaces appear in the desktop plugin browser alongside GitHub/git ones.

Error Messages/Logs

# Desktop main.log — two marketplaces registered, but only the official one's plugins are merged.
# 310 is exactly the number of plugins in claude-plugins-official at the time.
[NativeMarketplaceReader] Listing marketplaces (native)
[NativeMarketplaceReader] Found 2 marketplace(s)
[NativeMarketplaceReader] Listing available plugins (native)
[PluginsFetcher] fetchBrowsableRemotePlugins: 100 plugins (offset=0, hasMore=true)
[CustomPlugins] Fetched 100 remote plugins (100 org, 0 account)
[CustomPlugins] Merged 310 local + 100 remote = 410 total plugins

# After `claude plugin install <plugin>@<directory-marketplace>` from a terminal:
[CustomPlugins] Merged 311 local + 100 remote = 411 total plugins
# CLI sees the marketplace and its plugins fine
$ claude plugin marketplace list --json
[
  { "name": "my-local-mp", "source": "directory", "path": "C:\\src\\my-repo", "installLocation": "C:\\src\\my-repo" },
  { "name": "claude-plugins-official", "source": "github", "repo": "anthropics/claude-plugins-official", "installLocation": "C:\\Users\\me\\.claude\\plugins\\marketplaces\\claude-plugins-official" }
]

$ claude plugin list --json --available
... { "pluginId": "my-plugin@my-local-mp", "name": "my-plugin", "marketplaceName": "my-local-mp", "source": "./plugins/my-plugin" } ...

Steps to Reproduce

  1. Create a repo with a marketplace at its root:

``
my-repo/
.claude-plugin/marketplace.json
plugins/my-plugin/.claude-plugin/plugin.json
plugins/my-plugin/SKILL.md
.claude/settings.json
`
.claude-plugin/marketplace.json:
`json
{
"name": "my-local-mp",
"owner": { "name": "me" },
"plugins": [
{ "name": "my-plugin", "source": "./plugins/my-plugin", "description": "test plugin" }
]
}
`
.claude/settings.json:
`json
{
"extraKnownMarketplaces": {
"my-local-mp": { "source": { "source": "directory", "path": "." } }
}
}
``

  1. Open the repo in the Claude desktop app (Code tab) and trust the folder. ~/.claude/plugins/known_marketplaces.json now contains my-local-mp with "source": "directory" and installLocation = the repo path.
  2. Confirm the CLI sees it: claude plugin list --json --available lists my-plugin@my-local-mp.
  3. In the desktop app go to Customize → Plugins and browse/search for my-plugin.
  4. Result: it is not listed. main.log shows Found 2 marketplace(s) but the merged local count equals the official marketplace's plugin count only.
  5. Run claude plugin install my-plugin@my-local-mp in a terminal, reload Customize. Result: the plugin now appears (installed), confirming only discovery is affected.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.280 (Claude Code) — bundled with Claude desktop 2.7032.0.0 (Windows)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Other

Additional Information

Related but distinct reports, none of which name this mechanism:

  • #52147 – desktop UI has no way to add a local directory marketplace (closed, not planned)
  • #13509 – directory-based marketplace plugins inconsistent between browse and Installed tab
  • #96223 – desktop sessions load the cache copy of directory-marketplace plugins
  • #93466 – duplicate/misattributed cards in the desktop Directory → Plugins panel
  • #89413 – request to unify marketplace registration across CLI, desktop and Cowork

Suggested fix: in listAvailablePlugins, iterate the entries of known_marketplaces.json and read <installLocation>/.claude-plugin/marketplace.json for each, falling back to the marketplacesDir/<name> layout only when installLocation is absent. That matches how the CLI already resolves marketplaces (claude plugin list --available).

View original on GitHub ↗