Marketplace clone's fetch refspec is pinned to the ref kind at clone time: changing a marketplace `ref` from a tag to a branch freezes the clone permanently, and `marketplace update` still reports success

Status Fixed / completed
Reported on v2.1.228
Maintainer reply None cached
Activity 2 comments · opened Aug 11, 2026 · closed Aug 25, 2026

Bug

A git-sourced marketplace clone is created with a fetch refspec derived from the kind of ref that source.ref resolved to at clone time. If the configured ref later stops resolving under that refspec — for example because you moved from a moving tag to a branch of the same name — the refspec matches nothing, the clone is permanently frozen at whatever commit it holds, and claude plugin marketplace update <name> keeps reporting success.

Nothing anywhere reports a problem. The catalog silently serves an old plugin.json forever, so every consumer stays on an old plugin version.

Environment

Claude Code 2.1.228, Windows (win32 10.0.26100). Private GitHub marketplace declared through extraKnownMarketplaces with a pinned ref.

What we did

We used a moving annotated tag as the "released" pointer (ref: <name>), re-pointing it on each release. We then decided a branch was a better fit, so we deleted the remote tag and created a branch with the same name, pointing at the same commit.

Observed

The existing clone had been created while <name> was a tag:

$ git -C ~/.claude/plugins/marketplaces/<name> config --get remote.origin.fetch
+refs/tags/<name>:refs/tags/<name>

After the remote tag was deleted and replaced by a branch of the same name:

remote:  refs/heads/<name> -> <new commit>          (0.9.0)
clone:   HEAD              -> <old commit>          (0.6.0)
$ claude plugin marketplace update <name>
✔ Successfully updated marketplace: <name>
$ # catalog plugin.json still reports 0.6.0, HEAD still at the old commit

Repeating the update never helps. The refspec cannot match anything, because refs/tags/<name> no longer exists on the remote.

Two smaller observations from the same episode:

  1. While both existed, the stale local tag shadowed the new branch: git resolves refs/tags/ before refs/heads/, so git -C <clone> rev-parse <name> returned the old tag object even though a branch of that name existed on the remote. Deleting only the local tag is not enough either — the refspec is still tag-shaped, so subsequent fetches bring nothing.
  2. A deleted remote tag is not pruned from the clone by marketplace update, so the stale ref lingers indefinitely.

Repro

  1. Create a marketplace repo. Tag a commit rel (annotated) and declare {"source": {"source": "github", "repo": "...", "ref": "rel"}}.
  2. Install a plugin from it, confirm remote.origin.fetch is +refs/tags/rel:refs/tags/rel.
  3. Delete the remote tag rel; create a branch rel at a newer commit that bumps the plugin version.
  4. claude plugin marketplace update <name> → reports success, changes nothing. New sessions keep loading the old version.

Only working fix

Delete the clone and let it be recreated:

rm -rf ~/.claude/plugins/marketplaces/<name>
claude plugin marketplace update <name>

The fresh clone gets a branch-shaped refspec and immediately reaches the new commit:

+refs/heads/<name>:refs/remotes/origin/<name>

Installed plugin copies live under ~/.claude/plugins/cache/, a separate directory, so removing the marketplace clone does not disturb them. (claude plugin marketplace remove is not usable here — it uninstalls the plugins.)

Expected

Any of these would have prevented an unrecoverable-looking state:

  • Re-derive the refspec (or recreate the clone) when the configured ref no longer resolves under the stored refspec.
  • Have marketplace update fail loudly when the configured ref cannot be resolved on the remote, instead of printing success.
  • Prune deleted remote refs on update, and prefer refs/heads/<ref> over a local refs/tags/<ref> when both could match a configured ref.

The reporting behaviour is the more damaging half: a command that prints ✔ Successfully updated while doing nothing gives no reason to look further, and the resulting staleness has no other symptom.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗