Skip to content

fix(harness-ops): plugins sync and converge cannot clear stale project-scope pins the CLI treats as current #6713

Closed

Description

@kyle-sexton

Problem

/harness-ops:plugins reports project-scope pins as behind, but the CLI it delegates to treats them as current, so neither sync nor converge can clear the divergence.

Observed 2026-10-10 in a worktree of this repository, with harness-ops 3.11.0 loaded:

  • fleet-state.sh shows project-scope records for this worktree at older versions than user scope and the catalog: ai-slop 0.13.4 (catalog 0.13.6), evals 0.5.3 (0.7.2), harness-ops 3.7.2 (3.11.0), harness-config 1.8.1 (1.13.5), instruction-placement 0.21.5 (0.22.1), plugin-quality 0.13.5 (0.14.7), skill-quality 0.28.1 (0.29.1). All seven records carry lastUpdated 2026-10-04.
  • claude plugin update ai-slop@melodic-software -s project, run from the worktree root, prints "ai-slop is already at the latest version (0.13.6)". The same happens for evals ("0.7.2"). The record in installed_plugins.json stays at 0.13.4 / 0.5.3.
  • An earlier sync run reported evals@melodic-software (project): 0.5.3 → 0.5.4 as updated. The record stayed at 0.5.3.
  • The session loads the user-scope build (this skill ran from cache/melodic-software/harness-ops/3.11.0), not the 3.7.2 project pin, so the stale records do not seem to be what runs.

Impact

  • sync reports an update that did not change the record, and the next run counts the same divergence again.
  • converge's update strategy is a no-op for these rows. Its only other strategy uninstalls the pin, which edits the committed .claude/settings.json: too heavy for a record that does not affect what loads.

Questions

  • Does claude plugin update -s project compare against the effective (user-scope) install instead of the project record?
  • Should fleet-state.sh mark a project record as inert when the session loads a different version, and leave it out of the actionable divergence count?

Repro

  1. In a worktree whose installed_plugins.json project record for a plugin is older than user scope and the catalog, run /harness-ops:plugins sync.
  2. Run claude plugin update <id> -s project from the worktree root.
  3. Compare the record's version and lastUpdated before and after.

Activity

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 10, 2026
  2. github-actions commented on Oct 10, 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 10, 2026
  4. kyle-sexton commented on Oct 12, 2026

    @kyle-sexton
    MemberAuthor

    More data from a converge run on 2026-10-12 (harness-ops 3.13.2, Claude Code 2.1.296). All 166 lagging project pins whose directories exist were sent through the update strategy, (cd <projectPath> && claude plugin update <id> -s project). Every call exited 0, and the fleet state was re-read before each one.

    projectPath kind Pins Record actually moved
    Plain directory (not a git checkout) 43 43
    Main checkout of this repo 7 6 (discipline printed "already at the latest version (0.18.0)" and stayed at 0.17.7)
    git worktree checkouts of this repo (19) 116 0. Every call printed "already at the latest version", and the record kept its old version

    No .claude/settings.json changed anywhere, so the record in context/scope-semantics.md that says update -s project does not write committed settings still holds.

    This narrows the first question above: the no-op happens in linked worktrees, not for project scope in general. It looks like the CLI checks a record other than the worktree's own, probably the main checkout's. context/converge.md says worktrees "pin independently", but that was verified with uninstall on 2.1.228. With update, they do not behave independently.

    What this means for converge as written:

    • For worktree rows, the update strategy is a guaranteed no-op that still reports success. The only strategy that moves the record is uninstall, which edits each worktree's tracked settings file.
    • Suggestion: have converge flag worktree rows (git rev-parse --git-dir differs from --git-common-dir) as "update has no effect here", and re-read each record after its update so a no-op shows up as a failure instead of "ok".
  5. removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 12, 2026
  6. added theissue type on Oct 12, 2026
  7. kyle-sexton commented on Oct 13, 2026

    @kyle-sexton
    MemberAuthor

    Decision recorded. The owner approved applying independently verified recommendations on 2026-10-12.

    claude plugin update -s project reports success but leaves the record unchanged for pins in linked git worktrees (116 of 116 no-ops on 2026-10-12), so converge reports ok while nothing moves. The owner's comment proposes flagging worktree rows and re-reading each record after update; no PR exists.

    Verified by an independent fresh-context check (confirmed, risk low): gh issue view 6713 incl. kyle-sexton 2026-10-12T13:40Z comment (116 worktree pins, 0 moved; suggestion to flag worktree rows and re-read); plugins/harness-ops/skills/plugins/context/converge.md:87-90 (worktrees pin independently, verified with uninstall on 2.1.228); plugins/harness-ops/skills/plugins/context/scope-semantics.md:164-166; plugins/harness-ops/skills/plugins/scripts/fleet-state.sh exists; git log origin/main -- plugins/harness-ops/skills/plugins (no fix since; #6932 only adds a stale-record pointer); gh pr search 6713: no PR

    Admission to an agent lane stays with the work-class rules and owner ratification.

  8. kyle-sexton commented on Oct 13, 2026

    @kyle-sexton
    MemberAuthor

    Owner batch-ratified this admission in an attended session on 2026-10-12: work-class: scoped. Rule: class C1-C3, no trust surface, the independent check confirmed the recorded decision, and no owner step remains.

  9. added
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    and removed
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    on Oct 13, 2026
  10. kyle-sexton commented on Oct 14, 2026

    @kyle-sexton
    MemberAuthor

    Possibly the same root cause as #7445: on 2026-10-14, claude plugin update <id> -s project run from a repository's main checkout updated a sibling linked worktree's project-scope record instead of the current directory's. The CLI output named the worktree's path as the scope it updated. If the CLI matches project records by repository (the shared git common dir) or takes the first record for the id, rather than the literal cwd, it would also explain this issue's 'already at the latest version' from a worktree whose own record stays behind. #7445 has the evidence.

  11. added
    priority: mediumReal value, no hard deadline; normal backlog flow.
    and removed
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Oct 14, 2026
  12. kyle-sexton commented on Oct 18, 2026

    @kyle-sexton
    MemberAuthor

    Agent Brief

    Type: Bug
    Summary: Make /harness-ops:plugins converge and fleet-state report stale project-scope pins in linked worktrees honestly instead of claiming an update fixed them.

    Current behavior:
    In a linked git worktree, claude plugin update -s project reports success but leaves the project-scope record unchanged (116 of 116 no-ops measured). sync reports the update as done, the next run counts the same divergence again, and converge's update strategy is a silent no-op for those rows.

    Desired behavior:

    • Converge detects a linked worktree (the git dir differs from the git common dir) and marks its project-scope rows update has no effect here, instead of attempting or claiming an update.
    • After any update, converge and sync re-read the record; an update that left the version unchanged is reported as a failure, not a success.
    • fleet-state keeps a lagging project record in a linked worktree in the divergence count, labeled update ineffective; uninstall clears it.
    • Main checkouts behave as today.

    Key interfaces:

    • fleet-state output: a label on lagging project records in linked worktrees.
    • Converge's update strategy: worktree detection and a post-update re-read.
    • Sync's result classification: a no-op update counts as failed.

    Acceptance criteria:

    • A fleet-state test with a linked-worktree fixture shows the lagging record labeled update ineffective; uninstall clears it and still counted.
    • A test shows an update whose re-read version is unchanged classified as a failure.
    • The converge context states the worktree rule and the re-read.
    • scripts/run-test-file.sh plugins/harness-ops/skills/plugins/scripts/fleet-state.test.sh and scripts/run-test-file.sh plugins/harness-ops/skills/plugins/scripts/sync-run.test.sh pass.
    • A harness-ops changelog fragment is added and scripts/check-changelog-fragments.sh passes.
    • CI's check-skills passes on the PR.

    Out of scope:


    This was generated by AI during backlog shaping. Held, not admitted: this brief is drafted and checked, but the issue lane would stop on it today. It is admitted, by adding agent-ready, once the lane can run the skill-quality check scripts (Automation Lanes' PR #7912). Held under the owner's 2026-10-18 lane-narrowing course correction.

  13. added
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    and removed
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    on Oct 18, 2026
  14. kyle-sexton commented on Oct 20, 2026

    @kyle-sexton
    MemberAuthor

    Another data point against this on 2026-10-20 (harness-ops 3.17.0, WSL): claude plugin update <id> -s project run from /mnt/c/Users/<user> moved all 52 lagging project records to the catalog version. A fleet-state.sh read-back after each call confirmed it, and the folder's .claude/settings.json hash did not change. Together with #7458's 8 of 8, this looks closable unless a repro still exists on a current CLI.

  15. kyle-sexton commented on Oct 20, 2026

    @kyle-sexton
    MemberAuthor

    Still reproduces on a current CLI, from git worktrees (2026-10-20, Claude Code 2.1.304, harness-ops 3.17.0, WSL on melo-lap-001).

    What ran

    /harness-ops:plugins sync from each of 17 present checkouts of this repository (the main checkout plus 16 linked worktrees under ~/worktrees/). Together they held 144 project/local records behind the catalog, for example harness-ops 3.8.1–3.16.0 against 3.17.0.

    What happened

    • Sync's in-repo step issued 164 claude plugin update <id> -s project|local calls across the 17 runs. All 164 printed ✔ <name> is already at the latest version (<catalog version>). None printed updated from.
    • Every run's post.melodic-software.json still showed 144 records behind. For example, harness-ops@melodic-software in feat-netlify-plugin stayed at 3.8.9 (installPath …/harness-ops/3.8.9) in both pre and post.
    • Sync reported this silently. Each digest had in_repo.updated: [] and in_repo.failed: [], and the render said In-repo: 0 project/local install(s) updated with nothing under Action needed. A record the CLI claimed was current but that fleet-state.sh still shows as behind should be surfaced as "did not move", the same way converge Step 4 reports it.
    • Workaround that worked: claude plugin uninstall <id> -s local --keep-data, then claude plugin install <id> -s local, run from the main checkout. That moved docs-naming from 0.2.7 to 0.3.3 and left git status clean.

    Unattributed follow-on

    Between 04:50 and 04:56 UTC, after the last sweep run ended at 04:48, 141 of these worktree records moved to the catalog version (by lastUpdated in installed_plugins.json) without any further CLI call from this session. My one local-scope reinstall was at 04:52:25. A fresh fleet-state.sh read at about 05:00 showed 0 present records behind. I did not identify the writer. It could have been a delayed write from the earlier update calls, background autoUpdate, or another session on this lane. I can't tell from what I have, so this issue may still be real even though the end state is clean.

    The comment above, with 52 of 52 records moving from /mnt/c/Users/<user>, differs from this case in one way: these are linked worktrees of one repository that hold the same ids. That fits the #7445 sibling-record matching.

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.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