Plugin version resolution walks out of a non-git marketplace dir and records the user's own repo HEAD

Status Closed — duplicate
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Aug 2, 2026 · closed Aug 15, 2026

Summary

When a marketplace directory under ~/.claude/plugins/marketplaces/ is not a git
repository, plugin version resolution appears to run a git command with that directory as
the working directory. Git then walks up the filesystem, and if ~/.claude happens to
live inside the user's own git repository, it resolves to that repository instead. The
user's own HEAD is then written into installed_plugins.json as the installed plugin
version.

On my machine 17 of 30 plugin entries carry a commit SHA from my personal repository.

Environment

  • Claude Code 2.1.220, macOS
  • ~/.claude is inside a git repo rooted at $HOME (I version-control my config)
  • Marketplace: claude-plugins-official

Evidence

claude-plugins-official is materialized as a tarball drop, not a clone. It has no .git,
though known_marketplaces.json declares {"source": "github", "repo": "anthropics/claude-plugins-official"}:

$ ls -a ~/.claude/plugins/marketplaces/claude-plugins-official
.claude-plugin  .gcs-sha  .gitignore  external_plugins  LICENSE  plugins  README.md

So git walks up out of it:

$ cd ~/.claude/plugins/marketplaces/claude-plugins-official && git rev-parse --show-toplevel
/Users/<me>                     # my repo, not the marketplace

$ cd ~/.claude/plugins/marketplaces/claude-code-warp && git rev-parse --show-toplevel
~/.claude/plugins/marketplaces/claude-code-warp     # a real clone, unaffected

installed_plugins.json then records my HEAD:

"frontend-design@claude-plugins-official": [{
  "version": "2904b9bee772",
  "gitCommitSha": "2904b9bee772...",
  "installPath": ".../frontend-design/2904b9bee772",
  "lastUpdated": "2026-08-02T07:37:36.000Z"
}]

2904b9bee772 is a commit in my repository, message docs(fable-parity): ....

The part that makes it unambiguous

I measured this on two consecutive days without touching plugins.

  • 2026-08-01: my HEAD was 81c1517c2f9f. Nine plugins recorded version: "81c1517c2f9f".
  • 2026-08-02: I committed unrelated work; HEAD became 2904b9bee772. After a marketplace

refresh, the same nine plugins recorded version: "2904b9bee772".

No plugin was installed, updated, or removed between those readings. The recorded version
tracks my repository's HEAD.

Scope

  • 9 of 30 entries have version equal to a 12-char prefix of one of my commits.
  • 17 of 30 have a gitCommitSha that resolves as an object in my repository.
  • The wider set includes entries whose version looks like a clean semver, so the field

alone does not reveal the problem. security-guidance reads "version": "2.0.6" while
carrying gitCommitSha: 6a4debc2..., a merge commit in my repo from 2026-06-12.

  • Affected SHAs date back to 2026-04-20, so this is not new.

Why it is easy to miss

installed_plugins.json is covered by a .gitignore rule, so the churn never appears in
git status, and there is no history to recover a correct value from. The SHA is also used
as the installPath directory name, so cache directories are named after unrelated commits.

Impact

Version pinning is unreliable for any user whose ~/.claude sits inside a git repo, which
is a common way to version-control Claude Code configuration. Reproducing an environment,
diagnosing a plugin regression, or answering "which version am I running" all read a field
that is really a snapshot of the user's own unrelated work.

Suggested fix

Constrain version resolution to the marketplace directory rather than letting git walk up.
Any of these would do it:

  1. Resolve, then verify: run git -C <marketplace> rev-parse --show-toplevel and require

the result to equal <marketplace> before using the SHA.

  1. Set GIT_CEILING_DIRECTORIES to the marketplace's parent, or pass

--git-dir=<marketplace>/.git, so the walk cannot escape.

  1. For a tarball-materialized marketplace, prefer the .gcs-sha already written alongside

the content, and fall back to the plugin manifest's own version.

A guard for case 1 seems worthwhile regardless, since "the SHA I just read belongs to a
repository I do not control" is checkable and currently unchecked.

Happy to supply more detail or test a fix.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗