Repository navigation
fix(harness-ops): converge defaults to uninstalling project pins that mirror committed enabledPlugins entries #7446
Description
Scope (revised 2026-10-20, owner-confirmed)
Forward-updating lagging pins is sync's job (#7458). converge keeps only real scope consolidation:
- A project/local pin that a committed
enabledPluginsentry backs:convergenever proposesuninstall. When feat(harness-ops): sync should forward-update project pins in every present projectPath, not only the cwd's #7458 lands,syncupdates it, andconvergedoes not list it. Until then,convergeproposesupdate -s project. - A project/local pin with no committed entry behind it, duplicating user scope (someone ran
install -s projectby hand):convergeoffersuninstall -s project --keep-data, confirmed per row. - Catalog-regression BLOCKED rows and absent-path BLOCKED rows are unchanged.
What happened
/harness-ops:plugins converge (harness-ops 3.14.0, 2026-10-14) proposed its default strategy, claude plugin uninstall <id> -s project --keep-data, for every lagging project pin in this repository, because user scope held the highest version. Every one of those pins mirrored an entry in this repository's committed .claude/settings.json enabledPlugins.
Per context/converge.md:139-147 (Step 5) and context/scope-semantics.md, uninstall -s project always removes the id's enabledPlugins entry from the tracked settings file. Applying the default would have deleted committed enables that every clone and worktree of the repository reads once committed. Converge only detects this after the fact, in Step 5's diff classification ("Substantive").
claude plugin update <id> -s project brings the pin current and does not write the committed settings file (context/scope-semantics.md update exemption). That was the correct action for all of these rows.
Expected
Before choosing a strategy, converge checks whether the id is enabled in the tracked project settings at that projectPath. When it is, the default is update -s project, and uninstall is offered only as the non-default option with a warning that it removes a committed entry.
Repro
- In a repository whose committed
.claude/settings.jsonenables<id>underenabledPlugins, have a project-scope record for<id>older than the user-scope record. - Run
/harness-ops:plugins converge. - Step 2 proposes
uninstall -s project --keep-data(context/converge.md:47-52).
Evidence
Paths are relative to plugins/harness-ops/skills/plugins/ on main at 8718950e9d.
context/converge.md:47-52: the user-scope-highest branch defaults to uninstall with no check of the tracked settings.context/converge.md:139-158: Step 5 already knows uninstall writes the tracked file and classifies an entry removal as "Substantive", but only after the mutation.- In this run all of this repository's lagging project pins mirrored committed
enabledPluginsentries.
Proposed fix
- Step 2: for each project-scope row, read
<projectPath>/.claude/settings.jsonandgit -C <projectPath> ls-files --error-unmatch .claude/settings.json. If the file is tracked andenabledPlugins[<id>]exists, default toupdate -s project. - Keep uninstall as the default only when the id is absent from the tracked settings (a pin that exists only in
installed_plugins.json). - Show the reason for the chosen default in the preview row.
- Add an eval: a row with user-scope highest and a committed enable must propose update.
🤖 Generated with Claude Code
Activity
github-actions commentedon Oct 14, 2026
Thanks for the report. Automated triage handed this issue to a maintainer.
Automated triage. A maintainer reviews it.
kyle-sexton commentedon Oct 18, 2026
This was generated by AI during triage.
Verified: code path confirmed (not re-run: reproducing needs live user- and project-scope plugin records on a machine, and a wrong run would uninstall committed enables)
Default branch: e59e0be (still present)
Open linked PRs: none
plugins/harness-ops/skills/plugins/context/converge.md:46-52on main still defaults toclaude plugin uninstall <id> -s project --keep-datawhenever user scope holds the highest version, with no read of the tracked project settings. The only converge.md change since the report, #7812, checks thatupdate -s projectmoved the intended record and does not touch this branch.Triage: briefed, delegable (Bug, priority: medium, work-class: scoped).
Agent Brief
Type: Bug
Summary: Converge must not default to uninstalling a project pin that mirrors a committed
enabledPluginsentry.Current behavior: When user scope holds the highest version,
/harness-ops:plugins convergeproposes uninstalling each lagging project pin. For a pin whose id is enabled in the repository's tracked.claude/settings.json, that uninstall deletes the committed enable for every clone; converge notices only after the fact, in its diff classification.Desired behavior: Before choosing a strategy for a project-scope row, converge checks whether that project's
.claude/settings.jsonis tracked by git and enables the id. When it does, the default isupdate -s project, and uninstall is offered only as the non-default option with a warning that it removes a committed entry. Uninstall stays the default only when the id is absent from tracked settings. Each preview row shows why its default was chosen.Key interfaces:
Acceptance criteria:
update -s projectas the default.Out of scope:
kyle-sexton commentedon Oct 20, 2026
Reproduced on 2026-10-20 with harness-ops 3.17.0 on WSL.
convergestill defaults touninstall -s project --keep-datawhenever user scope holds the highest version. Run against 485 lagging pins in 51 present checkouts of this repo, it deleted the matchingenabledPluginsentries from each checkout's tracked.claude/settings.json(1–11 entries per file). Ten of the 11trueentries in that file are catalogdefaultEnabled: falseopt-ins, and cloud VMs depend on them (standardscomponents/cloud-environment/setup.sh:42-50). All 51 files were restored withgit restore.The intent for
converge, confirmed by the owner: every checkout that references these plugins (worktrees, feature branches) should load the latest version. That isupdate -s project, not removing the pin.