[BUG] Desktop plugin browser never lists plugins from a directory-source marketplace (listAvailablePlugins only scans ~/.claude/plugins/marketplaces/)
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 listshows it (Source: Directory (...))claude plugin validateon the marketplace manifest passesclaude plugin list --json --availableincludes 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
- 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": "." } }
}
}
- Open the repo in the Claude desktop app (Code tab) and trust the folder.
~/.claude/plugins/known_marketplaces.jsonnow containsmy-local-mpwith"source": "directory"andinstallLocation= the repo path. - Confirm the CLI sees it:
claude plugin list --json --availablelistsmy-plugin@my-local-mp. - In the desktop app go to Customize → Plugins and browse/search for
my-plugin. - Result: it is not listed.
main.logshowsFound 2 marketplace(s)but the merged local count equals the official marketplace's plugin count only. - Run
claude plugin install my-plugin@my-local-mpin 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).