Skip to content

fix(source-control): worktree teardown leaves project-scope plugin install records forever #3113

Description

@kyle-sexton

Problem

Removing a git worktree leaves behind every project-scope plugin install record that pointed into it. Claude Code keys project-scope installs in ~/.claude/plugins/installed_plugins.json by a literal projectPath, and nothing reaps them when that path goes away.

Measured on one machine, confirmed twice independently:

  • 108 project-scope install records across 8 marketplaces — melodic-software 45, claude-plugins-official 46, dotnet-agent-skills 10, anthropic-agent-skills 3, openai-codex 1, cloudflare 1, compound-engineering-plugin 1, karpathy-skills 1.
  • All 108 name one path: D:\worktrees\medley-p51, a worktree that no longer exists.
  • Distinct projectPath values machine-wide: 1. Every project-scope record on the machine is an orphan.

D:\worktrees is this plugin's configured worktree_root, and this plugin's own babysit_worktree_root option describes what lives there as "ephemeral" worktrees. These are lifecycle garbage from a directory this plugin created and destroyed.

plugins/source-control/skills/worktree/ greps clean for installed_plugins, plugin uninstall, pluginConfigs, and project-scope — the cleanup path removes the directory and stops.

Why this plugin owns it

claude-ops:plugins sees the wreckage (it reports these as actionable version skew and routes them to a converge that cannot run — see the sibling issue), but it must not reach across into another plugin's userConfig to decide what is "ephemeral", and it does not own the lifecycle. source-control creates the worktree, knows worktree_root, has worktree_stale_days, and destroys the directory. Reaping at teardown stops these at the source.

The safety boundary

Reap on a teardown this plugin performs, never on "a path that does not currently resolve". A project-scope record for a live repository on an unmounted network share or a detached external volume is indistinguishable from a dead worktree to a bare existence check, and destroying those records is real data loss.

Two further constraints:

  • Removal goes through claude plugin uninstall <id> -s project, never a direct edit of installed_plugins.json (Claude Code internal state, not a published contract).
  • -s project has no path flag — it acts on the literal cwd (verified 2.1.228, unchanged 2.1.240). A reap must therefore run while the directory still exists, i.e. before removal. If a reliable reap proves unachievable through the CLI, ship the detection-and-report half rather than something that silently half-works.

Pre-existing orphans (the 108 above) are not reachable by a teardown hook that did not exist when they were created — reporting them from status/audit is safe; reaping them is not, absent an explicit user action.

Related

No linked issue.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions