Skip to content

fix(harness-ops): converge defaults to uninstalling project pins that mirror committed enabledPlugins entries #7446

Closed

Description

@kyle-sexton

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 enabledPlugins entry backs: converge never proposes uninstall. When feat(harness-ops): sync should forward-update project pins in every present projectPath, not only the cwd's #7458 lands, sync updates it, and converge does not list it. Until then, converge proposes update -s project.
  • A project/local pin with no committed entry behind it, duplicating user scope (someone ran install -s project by hand): converge offers uninstall -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

  1. In a repository whose committed .claude/settings.json enables <id> under enabledPlugins, have a project-scope record for <id> older than the user-scope record.
  2. Run /harness-ops:plugins converge.
  3. 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 enabledPlugins entries.

Proposed fix

  1. Step 2: for each project-scope row, read <projectPath>/.claude/settings.json and git -C <projectPath> ls-files --error-unmatch .claude/settings.json. If the file is tracked and enabledPlugins[<id>] exists, default to update -s project.
  2. Keep uninstall as the default only when the id is absent from the tracked settings (a pin that exists only in installed_plugins.json).
  3. Show the reason for the chosen default in the preview row.
  4. Add an eval: a row with user-scope highest and a committed enable must propose update.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hnyd9hEzF6NuSFzC9rWj92

Activity

  1. added
    priority: highSignificant impact, or blocks an imminent release; staff this cycle.
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 14, 2026
  2. github-actions commented on Oct 14, 2026

    @github-actions
    Contributor

    Thanks for the report. Automated triage handed this issue to a maintainer.

    Automated triage. A maintainer reviews it.

  3. added
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    on Oct 14, 2026
  4. added theissue type on Oct 15, 2026
  5. added
    priority: mediumReal value, no hard deadline; normal backlog flow.
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    and removed
    priority: highSignificant impact, or blocks an imminent release; staff this cycle.
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 15, 2026
  6. kyle-sexton commented on Oct 18, 2026

    @kyle-sexton
    MemberAuthor

    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-52 on main still defaults to claude plugin uninstall <id> -s project --keep-data whenever user scope holds the highest version, with no read of the tracked project settings. The only converge.md change since the report, #7812, checks that update -s project moved 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 enabledPlugins entry.

    Current behavior: When user scope holds the highest version, /harness-ops:plugins converge proposes 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.json is tracked by git and enables the id. When it does, 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. Uninstall stays the default only when the id is absent from tracked settings. Each preview row shows why its default was chosen.

    Key interfaces:

    • converge strategy selection (Step 2): the tracked-settings check per project-scope row.
    • converge preview rows: a reason for the chosen default.

    Acceptance criteria:

    • An eval case with user scope highest and a committed enable for the id proposes update -s project as the default.
    • An eval case with user scope highest and no committed enable still proposes uninstall.
    • The preview shows the reason for each row's default.

    Out of scope:

  7. added
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    on Oct 18, 2026
  8. kyle-sexton commented on Oct 20, 2026

    @kyle-sexton
    MemberAuthor

    Reproduced on 2026-10-20 with harness-ops 3.17.0 on WSL. converge still defaults to uninstall -s project --keep-data whenever user scope holds the highest version. Run against 485 lagging pins in 51 present checkouts of this repo, it deleted the matching enabledPlugins entries from each checkout's tracked .claude/settings.json (1–11 entries per file). Ten of the 11 true entries in that file are catalog defaultEnabled: false opt-ins, and cloud VMs depend on them (standards components/cloud-environment/setup.sh:42-50). All 51 files were restored with git restore.

    The intent for converge, confirmed by the owner: every checkout that references these plugins (worktrees, feature branches) should load the latest version. That is update -s project, not removing the pin.

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

    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.priority: mediumReal value, no hard deadline; normal backlog flow.status: readyTriaged, unblocked, and fully specified; eligible to pick up.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions