anthropics / anthropics/claude-code

[BUG] A directory marketplace whose recorded installLocation no longer exists never loads again: every launch re-fetches from source but keeps the dead path, and claude plugin marketplace update fails on it

オープン
#94,452 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
area:plugins bug has repro platform:wsl
主要言語
Python
スター
145k
フォーク
23.1k
PR マージ指標
PR 指標を取得中

説明

### 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?

Take a `directory`-source marketplace whose `installLocation` in `known_marketplaces.json` points at a path that no longer exists, while its `source.path` still exists. That marketplace's plugins never load again.

Every launch notices the problem: `Cache corrupted or missing for marketplace mkt-a, re-fetching from source: ENOENT …`. It bumps the entry's `lastUpdated` and **leaves `installLocation` pointing at the dead path**, so the next launch misses in exactly the same way. The file gets rewritten on every launch, just never the field that is wrong.

The CLI doesn't fix it either:

- `claude plugin marketplace update mkt-a` fails on the dead path (`Failed to refresh marketplace 'mkt-a': ENOENT … '/nonexistent/mkt-a'`) instead of resolving from `source`.
- `claude plugin list` shows `✘ failed to load — Marketplace mkt-a failed to load: cache-miss`, and nothing tells the user which command recovers it.
- The one recovery that works, `claude plugin marketplace remove` then `add`, does write a correct `installLocation`. But `remove` also deletes the marketplace's plugins from `enabledPlugins`. After "recovering", the plugin still doesn't load, because it is now disabled.

For a directory source, Claude Code itself records `installLocation` equal to `source.path` when it adds the marketplace. So once the two diverge (for example, the file was carried across machines or config dirs; cf. #36575, #82272), the entry stays stuck, even though the correct value is sitting in the same entry.

### What Should Happen?

1. When a `directory` source is re-fetched because the recorded `installLocation` is missing, write the resolved `source.path` back as `installLocation`.
2. `claude plugin marketplace update` should resolve a directory marketplace from `source`, not from the recorded location.
3. `remove` + `add` should not be the only recovery, given that `remove` drops the marketplace's plugins from `enabledPlugins` (#82272 notes the same side effect).

### Error Messages/Logs

```shell
# every launch (debug log)
[DEBUG] Registered 1 hooks from 1 plugins
[WARN] Cache corrupted or missing for marketplace mkt-a, re-fetching from source: ENOENT: no such file or directory, open '/nonexistent/mkt-a'

$ claude plugin marketplace update mkt-a
Updating marketplace: mkt-a...✘ Failed to update marketplace(s): Failed to refresh marketplace 'mkt-a': ENOENT: no such file or directory, open '/nonexistent/mkt-a'
```

### Steps to Reproduce

Runs against an isolated config dir. No login is needed, because plugin loading runs before the auth check (each `-p` launch exits with `Not logged in`, which does not affect the result).

```bash
# Isolated config dir; no login needed (plugin loading runs before the auth check).
R=$(mktemp -d); mkdir -p "$R/cfg/plugins" "$R/home"
for m in a b; do
d="$R/mkt-$m"; p="$d/plugins/plugin-$m"
mkdir -p "$d/.claude-plugin" "$p/.claude-plugin" "$p/hooks"
echo "{\"name\":\"mkt-$m\",\"owner\":{\"name\":\"demo\"},\"plugins\":[{\"name\":\"plugin-$m\",\"source\":\"./plugins/plugin-$m\"}]}" > "$d/.claude-plugin/marketplace.json"
echo "{\"name\":\"plugin-$m\",\"version\":\"0.0.1\"}" > "$p/.claude-plugin/plugin.json"
echo '{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"true"}]}]}}' > "$p/hooks/hooks.json"
done
cat > "$R/cfg/settings.json" < "$KM" </dev/null 2>&1
echo "-- launch $i"; grep -hE 'Registered|Cache corrupted' "$R/launch$i.log" | cut -c26-
grep -o '"installLocation": *"/nonexistent[^"]*"\|"lastUpdated": *"[^"]*"' "$KM" | head -1
done
cc plugin marketplace update mkt-a
cc plugin list

echo "== the CLI recovery: remove + add"
cc plugin marketplace remove mkt-a
grep -A3 enabledPlugins "$R/cfg/settings.json"
cc plugin marketplace add "$R/mkt-a"
cc -p noop --debug-file "$R/launch3.log" >/dev/null 2>&1
grep -h 'Registered' "$R/launch3.log" | cut -c26-
cc plugin list
```

Observed on 2.1.272 (verbatim; ANSI colour codes stripped):

```
== mkt-a's directory source exists, but its recorded installLocation does not
-- launch 1
[DEBUG] Registered 1 hooks from 1 plugins
[WARN] Cache corrupted or missing for marketplace mkt-a, re-fetching from source: ENOENT: no such file or directory, open '/nonexistent/mkt-a'
"installLocation": "/nonexistent/mkt-a"
-- launch 2
[DEBUG] Registered 1 hooks from 1 plugins
[WARN] Cache corrupted or missing for marketplace mkt-a, re-fetching from source: ENOENT: no such file or directory, open '/nonexistent/mkt-a'
"installLocation": "/nonexistent/mkt-a"
Updating marketplace: mkt-a...✘ Failed to update marketplace(s): Failed to refresh marketplace 'mkt-a': ENOENT: no such file or directory, open '/nonexistent/mkt-a'

Installed plugins:

❯ plugin-a@mkt-a
Version: unknown
Scope: user
Status: ✘ failed to load
Error: Marketplace mkt-a failed to load: cache-miss

❯ plugin-b@mkt-b
Version: 0.0.1
Scope: user
Status: ✔ enabled

== the CLI recovery: remove + add
✔ Successfully removed marketplace: mkt-a
"enabledPlugins": {
"plugin-b@mkt-b": true
},
"extraKnownMarketplaces": {
Adding marketplace…✔ Successfully added marketplace: mkt-a (declared in user settings)
[DEBUG] Registered 1 hooks from 1 plugins
Installed plugins:

❯ plugin-b@mkt-b
Version: 0.0.1
Scope: user
Status: ✔ enabled
```

Both launches load only `plugin-b`, and `installLocation` stays dead even though the entry is rewritten each time (its `lastUpdated` changes). After `remove` + `add`, `plugin-a@mkt-a` is gone from `enabledPlugins` and from `claude plugin list`, so the next launch still loads one plugin.

### Claude Model

Not applicable. The failure happens before any model call.

### Is this a regression?

I don't know. I haven't tested older versions.

### Last Working Version

_No response_

### Claude Code Version

2.1.272 (Claude Code)

### Platform

Other. Reproduces without authentication.

### Operating System

Ubuntu/Debian Linux (WSL2)

### Terminal/Shell

bash, non-interactive (`claude -p` and `claude plugin …` subcommands)

### Additional Information

Related, but not the same bug:
- #82272: `installLocation` is validated by string prefix, so a symlinked config dir is flagged as corrupted. Here the path really does not exist, on a single config dir.
- #36575 (closed): non-portable `installLocation` formats.
- #94451: the sibling registry bug, where an invalid `known_marketplaces.json` is never repaired.

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。