claude plugin install refreshes the marketplace, sees a newer version, and still reports "already installed" without upgrading (2.1.232)
Summary
claude plugin install <plugin>@<marketplace> on an already-installed plugin prints a ✔ success
glyph, exits 0, and does not upgrade — even when the marketplace offers a strictly newer version.
On 2.1.232 this is sharper than it sounds, because that release added "/plugin install now refreshes the marketplace first." That refresh fires, and it does not help.
plugin@marketplace
The marketplace clone advances to the newer version during the install command, and the command then
reports "already installed" anyway. The already-installed check appears to run after the refresh but
before any version comparison.
claude plugin list shows no staleness signal either, so there is no non-manual way to notice. The
incorrect end state is indistinguishable from the correct one without reading~/.claude/plugins/installed_plugins.json by hand.
Repro A — git marketplace (the real-world shape)
Verified on 2.1.232, Windows 11, Git Bash. Throwaway private GitHub repo containing only a.claude-plugin/marketplace.json, a plugin.json, and one no-op command.
- Repo at
"version": "1.0.0". Register and install:
```
claude plugin marketplace add <owner>/<repo>
claude plugin install install-repro-plugin@plugin-install-repro --scope user
✔ Successfully installed
→ . installed_plugins.json records 1.0.0 andgitCommitSha: b6e739c…`.
- Bump
plugin.jsonto"version": "1.1.0", commit, push. Confirm the split state:
| | Version |
|---|---|
| Remote HEAD | 1.1.0 |
| Local marketplace clone | 1.0.0 (stale) |
| Installed plugin | 1.0.0 |
- Run
installagain — nomarketplace updatefirst, deliberately, to exercise 2.1.232's
auto-refresh:
````
$ claude plugin install install-repro-plugin@plugin-install-repro --scope user
Installing plugin "install-repro-plugin@plugin-install-repro"...✔ Plugin "install-repro-plugin@plugin-install-repro" is already installed (scope: user)
$ echo $?
0
- The refresh worked. The install did not. Immediately after that single command:
| | Version | |
|---|---|---|
| Local marketplace clone | 1.1.0 | ← advanced by this install command |
| Installed plugin | 1.0.0 | ← unchanged |
installPath still ends \cache\plugin-install-repro\install-repro-plugin\1.0.0, version is
still 1.0.0, and gitCommitSha is still b6e739c….
So the command fetched the newer catalog, had 1.1.0 in hand, and reported success without using it.
Nothing in the output mentions 1.1.0 exists or that plugin update would apply it.
- Running
installa third time — clone now unambiguously fresh — behaves identically.
claude plugin listgives no hint, even though the clone it resolves against is at 1.1.0:
````
❯ install-repro-plugin@plugin-install-repro
Version: 1.0.0
Scope: user
Status: ✔ enabled
claude plugin updatedoes the right thing, confirming the newer version was resolvable the whole
time:
```
✔ Plugin "install-repro-plugin" updated from 1.0.0 to 1.1.0 for scope user. Restart to apply changes.
gitCommitSha
moves to 99d1e17…`.
Repro B — directory marketplace (control: removes staleness entirely)
Same result with a directory-source marketplace, which is worth including because it isolates the
defect from catalog freshness completely. A directory marketplace records installLocation as the
source path itself and reads it live, so its catalog cannot be stale.
- Local dir plugin at
1.0.0;claude plugin marketplace add /path; install →1.0.0. - Edit the source
plugin.jsonto1.1.0. claude plugin install …→✔ already installed, exit 0, still1.0.0.claude plugin marketplace update …→✔ Successfully updated; install again → still1.0.0.claude plugin update …→1.0.0→1.1.0.
Repro A shows the behaviour where consumers actually meet it. Repro B shows it is not a
catalog-freshness problem in any form, since there is nothing there that can be stale.
Observed vs expected
Observed: success glyph, exit 0, no upgrade, no mention of the newer version that was just
fetched, and no staleness signal from plugin list.
Expected: one of —
- (a)
installperforms the upgrade — it already resolves the newer version fine, as step 7 shows; or - (b)
installreports the delta and names the corrective command, e.g.
````
already installed at 1.0.0; marketplace offers 1.1.0
run 'claude plugin update install-repro-plugin@plugin-install-repro' to upgrade
and exits non-zero.
Either removes the false-success signal. (b) seems preferable — it keeps install and update
semantically distinct while making the no-op legible. The information needed for that message is
demonstrably already in hand at that point, since the refresh has just completed.
Second, smaller ask
claude plugin list should flag when an installed version is behind what its marketplace offers.
Today Version: 1.0.0 + Status: ✔ enabled reads as healthy while the plugin is a version behind a
catalog already sitting on disk.
Impact
The failure is quiet, and it lands on the person trying to be careful. Every available signal agrees
the upgrade succeeded, so the natural next step is to start testing — against the old build.
We hit this while verifying a prerelease of an internal plugin. The install reported success, and the
discrepancy was only caught by manually reading installed_plugins.json. A full verification run came
close to reporting confident conclusions about a build that was never loaded.
What makes it especially quiet is that the still-loaded old version behaves correctly for itself —
our plugin's own SessionStart hook kept printing the message the previous version was written to
print, which is exactly what a healthy session looks like. A stale plugin has no reason to look
unwell, so the only contradicting evidence is a file on disk.
Two further things make it hard to catch without knowing to look:
- Restart is required for a plugin change to take effect, so nothing inside the running session
contradicts the stale version.
- Cache directories are not a version signal. Multiple versions coexist under
cache/<marketplace>/<plugin>/, so their presence proves nothing about what is active. (This has
improved — uninstalled versions now get an .orphaned_at marker, which genuinely helps.)
Related, and why this is distinct
Checked for existing coverage first:
- #52206, #51773 — request documentation of reinstall-as-repair and dependency behaviour.
Adjacent, but they ask for docs describing current behaviour rather than reporting that it emits a
false success when a newer version is available.
- #79950 —
plugin updatereporting "already at the latest version" when a git marketplace's
declared version string does not change. Different command, different cause; here plugin update is
the thing that works.
Several changelog entries have narrowed adjacent cases without covering this one:
| Release | Entry | Bearing |
|---|---|---|
| 2.1.97 | Fixed plugin update reporting "already at the latest version" for git marketplaces with newer commits | Fixes update, not install |
| 2.1.101 | /plugin and plugin update warn when the marketplace could not be refreshed instead of silently reporting a stale version | Warns on refresh failure; here refresh succeeds |
| 2.1.117 | plugin install on an already-installed plugin now installs missing dependencies instead of stopping at "already installed" | Closest existing change — but only dependencies; the plugin's own version is still not compared |
| 2.1.221 | /plugin install refreshes a stale catalog and retries before reporting not-found | Applies to not-found, not to already-installed |
| 2.1.232 | /plugin install refreshes the marketplace first | Confirmed firing in Repro A step 4 — and it does not help |
2.1.117 is the nearest miss: install no longer unconditionally short-circuits on "already
installed", but the exception carved out was dependency resolution. The plugin's own version still
isn't considered.
Environment
- Claude Code 2.1.232
- Windows 11 Pro 26200, Git Bash (MSYS2)
- Reproduced with both
githubanddirectorymarketplace sources - Scope:
user
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗