[BUG] Plugin version resolution escapes the marketplace root and adopts ~/.claude HEAD, causing per-commit re-cloning
Summary
When a marketplace is distributed without a .git directory (e.g. the GCS-tarball claude-plugins-official) and one of its plugins declares "version": null, version resolution falls back to a git SHA lookup that walks up the filesystem and resolves against the user's config repo, ~/.claude/.git.
The consequence is that a plugin's recorded version and gitCommitSha become the user's own ~/.claude commit SHA. Every commit the user makes in ~/.claude therefore re-versions and re-clones every null-version plugin from that marketplace into a fresh cache directory.
Evidence
The marketplace directory has no .git, so git resolves to the enclosing repo:
$ ls -a ~/.claude/plugins/marketplaces/claude-plugins-official | grep -E '^\.(git|gcs)'
.gcs-sha
$ git -C ~/.claude/plugins/marketplaces/claude-plugins-official rev-parse --show-toplevel
/Users/kerf/.claude
$ git -C ~/.claude/plugins/marketplaces/claude-plugins-official rev-parse HEAD
b73bf99cd9bca6404c75cf1dcbfac648897174ee
$ git -C ~/.claude rev-parse HEAD
b73bf99cd9bca6404c75cf1dcbfac648897174ee # identical
And that value is what lands in the install manifest:
$ jq '.plugins["serena@claude-plugins-official"][0] | {version, gitCommitSha}' \
~/.claude/plugins/installed_plugins.json
{
"version": "69d207dd62a3",
"gitCommitSha": "69d207dd62a3dfb6da1d4f842cc4e3894a3db9bc"
}
69d207dd62a3… is not an upstream commit — it is my local commit "chore: sync plans, project memory, plugin and manifest state" in ~/.claude. The previously recorded value, d7a47afb78d7, was likewise my earlier commit "feat(stack): add paedARweight to manifest".
Positive controls
Plugins that do declare a version are unaffected, which isolates the trigger to version: null:
| Plugin | plugin.json version | Recorded version | Churns? |
|---|---|---|---|
| telegram (external) | 0.0.6 | 0.0.6 | no |
| code-simplifier (bundled) | 1.0.0 | 1.0.0 | no |
| serena (external) | null | <~/.claude HEAD> | yes |
Scope is likewise isolated to the tarball marketplace — of 12 configured marketplaces, 11 are git clones where a SHA version is legitimately that marketplace's own commit. Only claude-plugins-official lacks a .git boundary.
Impact
- Cache growth is a per-commit ratchet. Each
~/.claudecommit leaves a complete duplicate set. Four commits in one session produced a full extra copy of 9 plugins; the cache reached 1.7 GB. version/gitCommitShaare meaningless for these plugins. They identify the user's config repo, not the plugin. This undermines the provenance goal of #78311 and makes reproducible pinning impossible for exactly the plugins that most need it.- Mid-session re-materialization. Because the re-version fires whenever the user commits, superseded directories are left behind still carrying
.in_usemarkers while a live session holds them — the same failure surface as #73952. - It produces false "out of date" diagnoses. The recorded
versionand the marketplace's.gcs-shalook like comparable identifiers and are not, so tooling that diffs them reports plugins as "behind" when their cached contents are byte-identical to the marketplace source.
Affected plugins
code-review, context7, feature-dev, frontend-design, linear, playwright, pr-review-toolkit, serena, skill-creator — all claude-plugins-official, all "version": null.
Expected behaviour
Version resolution should not escape the marketplace root. Options, roughly in order of preference:
- Use the marketplace's own identifier when present —
.gcs-shais already on disk for exactly this case. - Bound the git lookup to the marketplace directory (
git rev-parse --show-toplevelmust equal the marketplace root, otherwise treat as "no git source"). - Fail closed to a stable sentinel rather than silently adopting an unrelated repository's HEAD.
Adding version fields to the 9 plugin manifests would fix today's instance, but any future null-version plugin in a tarball marketplace regresses identically, so the fallback itself seems worth bounding.
Environment
- Claude Code on macOS (darwin 25.5.0)
~/.claudeis itself a git repo (config tracked in version control)- Marketplace
claude-plugins-official, GCS tarball,.gcs-sha=cf99fc252a44e3f36763abe1db8744757f1b0297
Related
#78311 (provenance for installed plugins), #77546 (loader selects older cache than the manifest), #73952 (mid-session re-materialization breaks pinned paths), #69020 (stale cached plugin version).
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗