[BUG] Plugin version resolution escapes the marketplace root and adopts ~/.claude HEAD, causing per-commit re-cloning

Status Open
Maintainer reply None cached
Activity 2 comments · opened Jul 30, 2026

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

  1. Cache growth is a per-commit ratchet. Each ~/.claude commit leaves a complete duplicate set. Four commits in one session produced a full extra copy of 9 plugins; the cache reached 1.7 GB.
  2. version / gitCommitSha are 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.
  3. Mid-session re-materialization. Because the re-version fires whenever the user commits, superseded directories are left behind still carrying .in_use markers while a live session holds them — the same failure surface as #73952.
  4. It produces false "out of date" diagnoses. The recorded version and the marketplace's .gcs-sha look 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-sha is already on disk for exactly this case.
  • Bound the git lookup to the marketplace directory (git rev-parse --show-toplevel must 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)
  • ~/.claude is 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).

View original on GitHub ↗

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