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

Abierto
#94,452 0 comentarios 0 reacciones 0 asignados Ver en GitHub
area:plugins bug has repro platform:wsl
Lenguaje dominante
Python
Estrellas
145k
Forks
23.1k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

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

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.