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 ✓ Yes — bcherny
Activity 3 comments · opened Aug 11, 2026 · closed Aug 25, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

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 ↗

3 Comments

alisettar · 2 months ago

One clarification to keep the scope of this precise, in case it reads as "fetch cannot move a tag":

The stored refspec is force-prefixed (+refs/tags/<name>:refs/tags/<name>), so while the remote tag still existed, fetching did move it — an explicit claude plugin marketplace update <name> advanced the clone from one tagged commit to the next every time. A moving tag on its own is therefore handled correctly by this refspec.

The failure begins only once the configured ref no longer exists in the namespace the refspec targets: after the remote tag is deleted, +refs/tags/<name>:... can never match, so the clone is stuck and the update command still reports success. So the fix worth considering is not "force-fetch tags" but rather: re-derive the refspec (or recreate the clone) when the configured ref no longer resolves under the stored one, and fail loudly instead of printing ✔ Successfully updated.

bcherny collaborator · 1 month ago

Thanks for the unusually thorough report — the refspec analysis made this easy to retest.

I tried to reproduce this on the current release (2.1.233) with a marketplace pinned to an annotated tag, then deleting the remote tag and replacing it with a same-named branch at a newer commit:

  • The initial clone did get the tag-shaped fetch refspec, as you observed.
  • But claude plugin marketplace update <name> picked up the new branch commit anyway — the configured ref is re-resolved on update, the clone advanced, and the catalog served the new plugin version. I verified it was actually tracking the branch (not the default branch) by moving the default branch past it.
  • If the configured ref doesn't resolve on the remote at all, update now surfaces the underlying git error instead of reporting success.

So the frozen-clone behavior appears to be fixed in a recent release. The one remnant I could still see is that a stale local tag isn't pruned from the clone, but it no longer affects which commit the catalog serves.

Could you upgrade to the latest version and run claude plugin marketplace update <name> again? If your clone is still stuck after upgrading, deleting the clone directory once (as you found) clears the old state — and please reopen with the output if the staleness comes back after that.

🤖 Generated with Claude Code

github-actions[bot] · 15 hours ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.