Skip to content

fix(claude-ops): widen the settings-write facts to every enabledPlugins call - #2527

Merged
kyle-sexton merged 4 commits into
mainfrom
fix/claude-ops-enabledplugins-write-facts
Aug 12, 2026
Merged

fix(claude-ops): widen the settings-write facts to every enabledPlugins call#2527
kyle-sexton merged 4 commits into
mainfrom
fix/claude-ops-enabledplugins-write-facts

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #2519, which merged before its review findings were addressed. Both findings were real;
both are now confirmed by experiment on Claude Code 2.1.228 rather than by wording judgment.

1. "converge is the only action this skill runs that surfaces a settings diff" was false.
sync.md Step 5 issues claude plugin enable <id> -s <that scope>, and enable sets the same
enabledPlugins[id] key install was verified to write. Tested, one call each against a clean
tracked .claude/settings.json:

Call Writes .claude/settings.json?
install <id> -s project yes
uninstall <id> -s project yes
enable <id> -s project yes (adds the id, true)
disable <id> -s project yes (adds the id, false)
update <id> -s project no

-s local writes the gitignored .claude/settings.local.json and never touches the tracked file —
tested, not assumed, since Step 5 issues both. So sync, the default action, can dirty a
team-shared tracked file with no diff surfaced. This change documents that exposure and tells Step 5
to name a -s project enable in the report; giving Step 5 converge's full diff-surfacing is a
behavior change to the default action and is deliberately left to its own PR.

Also recorded: enable -s project gates on the merged effective value — enabling an id that is
true only at user scope fails rather than writing a project entry.

2. Project-scope keying: both the original wording and the proposed correction were wrong.
#2519 said "keys on the working directory"; review proposed "checkout root", citing fleet-state.sh
and its test. Testing settled it against both:

  • The CLI keys on the literal cwd — install -s project from <checkout>/nested/subdir recorded
    that subdirectory as projectPath and created its own nested/subdir/.claude/settings.json.
  • fleet-state.sh resolves the checkout root (fleet-state.sh:211-221, read directly rather
    than taken from the review), and fleet-state.test.sh pins the nested-subdirectory case.

The layers disagree, and the gap is the finding, not either wording: a project-scope install from
below the checkout root never matches currentProject, never updates, and never appears in a
divergence row — while still loading in that subtree. Recorded in gotchas.md, where the sibling
silent-mismatch gotchas live. converge.md Step 2 keeps only the worktree consequence the evidence
supports.

Test plan

  • scripts/affected-tests.sh --run — 3 suites selected, all pass (check-skill.test.sh,
    check-changed-skills.test.sh).
  • markdownlint-cli2 on all changed markdown — 0 issues.
  • lychee --offline — 0 errors.
  • check-skill.sh on plugins/claude-ops/skills/pluginsPASS, 0 errors, 0 warnings.
  • plugin.json parses; version 0.31.7 → 0.31.8.
  • The released ## [0.31.7] CHANGELOG section was diffed against origin/main and is byte-identical
    — this PR adds ## [0.31.8] above it rather than editing shipped release notes.
  • Every experiment torn down: scratch project-scope installs removed, the nested/ tree deleted, the
    scratch settings.local.json removed, and both checkouts left clean.

Related

Closes #2526

Follows #2519. Upstream context for the underlying CLI behavior is in
anthropics/claude-code#81058, where this session's 2.1.228 findings are posted as a comment.

…ns call

Review found two defects in the first pass, both confirmed by experiment.

`scope-semantics.md` claimed converge was the only action surfacing a settings
diff. False by this change's own evidence: sync's Step 5 issues `enable -s <that
scope>`, and `enable -s project` writes the committed file exactly as install
does. The section is now a verified table — install, uninstall, enable, disable
write at project scope; update does not; `-s local` writes the gitignored
settings.local.json — and sync.md Step 5 names the exposure, with the
diff-surfacing remediation tracked separately rather than widened into this
change.

The "keys on the working directory" wording drew a correction toward "checkout
root". Testing settled it against both readings: `install -s project` from
`<checkout>/nested/subdir` records that subdirectory as projectPath and creates
its own .claude/settings.json, so the CLI is cwd-keyed — while fleet-state.sh
resolves the checkout root (fleet-state.sh:211-221). The layers disagree, and
that gap is the finding: a plugin installed below the checkout root never
matches currentProject, never updates, and never appears in a divergence row
while still loading in that subtree. Recorded in gotchas.md; converge.md Step 2
keeps only the worktree consequence it can support.

Also recorded: `enable -s project` gates on the merged effective value, so
enabling an id true only at user scope fails rather than writing a project entry.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

The cherry-pick carried this change's notes inside the already-released 0.31.7
section. Restore that section to the shipped text and open 0.31.8 above it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 55s —— View job


Security review

  • Gather context (PR diff, changed files)
  • Apply skip gate
  • Review diff for security issues
  • Post findings

Skip gate: no security-relevant surface. All six changed files are documentation and metadata:

  • plugins/claude-ops/.claude-plugin/plugin.json — version bump only (0.31.7 → 0.31.8)
  • plugins/claude-ops/CHANGELOG.md — new changelog entries
  • plugins/claude-ops/skills/plugins/context/converge.md, gotchas.md, scope-semantics.md, sync.md — skill guidance prose describing observed claude plugin CLI write behavior (which scopes/actions touch .claude/settings.json vs .claude/settings.local.json)

No executable code, scripts, or GitHub Actions workflows are touched. There is no new input handling, no command construction, no credential/token/secret handling, and no change to CI permissions or trigger patterns. The content documents existing CLI scope semantics for a human/agent operator to read; it does not itself execute anything security-sensitive.

No security issues found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7847c3c68

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/claude-ops/CHANGELOG.md
Comment thread plugins/claude-ops/skills/plugins/context/sync.md
Comment thread plugins/claude-ops/skills/plugins/context/gotchas.md Outdated
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

Last security-reviewed head: ad8bfd038145316db30a65335dc2fd87b496b10c. On the next push, the relevance gate compares only the commits since this SHA; delete this comment to force a full re-review.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 34s —— View job


Code review complete

  • Gather context (PR body, diff, CLAUDE.md, REVIEW.md)
  • Review changed files for correctness / maintainability
  • Post findings

This repo carries .github/workflows/claude-security-review.yml, so security findings are scoped to that lane and omitted here.

🔴 Important — the PR's own new evidence contradicts two unrevised "converge is the only writer" claims

This PR documents, with fresh empirical verification, that sync's Step 5 (enable <id> -s <that scope>) writes the committed .claude/settings.json — see the new sync.md Step 5 exposure note and the scope-semantics.md write-behavior table. Critically, Step 5 has no confirm gate — grepping sync.md shows the only AskUserQuestion gate is on the unrelated install_new step; Step 5 runs enable -s project unconditionally for any completeness gap it finds.

That directly contradicts two claims this PR leaves untouched, both load-bearing for how a reader trusts the rest of the skill:

  • converge.md:3"converge is the only action that can touch a committed .claude/settings.json. It never runs implicitly from sync..."
  • SKILL.md:35-37"Never silently fixes drift it finds. sync mutates only via the documented CLI actions below; converge is the one action that can touch a committed .claude/settings.json, and only after an explicit per-plugin confirm."

Both statements are now false by this PR's own findings: sync's default action can write the committed file silently, with no confirm and no diff surfaced. The PR's cover letter explicitly scopes giving Step 5 "converge's full diff-surfacing" to a follow-up PR, which is a reasonable behavior-change boundary — but the two absolute "only action" claims elsewhere in the same skill aren't a behavior change to defer, they're now-incorrect statements of fact that a reader (or a future agent following this skill) will act on. Recommend softening both to something like "the only action that surfaces/reviews the resulting diff" (which remains true) in this PR or the named follow-up, so the skill doesn't carry two documented invariants its own new section disproves.

Fix this →

Otherwise

The rest of the diff is careful, self-consistent documentation work: the new write-behavior table, the enable -s project merged-effective-value gate, and the project-scope cwd-vs-checkout-root split were all cross-checked against fleet-state.sh:211-221 and fleet-state.test.sh, and line up correctly. CHANGELOG entry and version bump are consistent with the diff.

…d spot

Address review threads: entrypoint now names sync's enable -s project write path;
gotchas narrows the subdirectory claim to currentProject-only operations.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @cursor[bot]'s task in 34s —— View job


Security review

  • Gather context (PR diff, commits since last reviewed head)
  • Apply skip gate
  • Review diff for security issues
  • Post findings

Skip gate: no security-relevant surface. Per the prior review comment, the last security-reviewed head was c54b0ba; this run covers only the commits since then, up to f605b88 (1 new commit: "docs(claude-ops): reconcile sync write exposure and subdirectory blind spot").

That commit touches three files, all documentation prose in the claude-ops skill:

  • plugins/claude-ops/skills/plugins/SKILL.md — clarifies that sync (not just converge) can write committed settings via enable -s project
  • plugins/claude-ops/skills/plugins/context/converge.md — same clarification in the converge entrypoint preamble
  • plugins/claude-ops/skills/plugins/context/gotchas.md — narrows the subdirectory-blind-spot claim to currentProject checks specifically, noting converge can still target the divergence row via another scope record

These address the two Codex review findings from the prior push (version-bump/changelog parity and the subdirectory blind-spot scoping). No executable code, scripts, or GitHub Actions workflows are touched in this range — it's skill guidance prose only, describing existing CLI/tooling behavior for a human/agent operator to read. No new input handling, no command construction, no credential/secret handling, no CI permission or trigger changes.

No security issues found.

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 3 times. The lane skips further automatic reviews after 5; deleting this comment resets the count.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @cursor[bot]'s task in 3m 26s —— View job


Code review

  • Gather context (PR diff, changed files, CLAUDE.md/REVIEW.md)
  • Apply skip gate
  • Review diff for correctness/maintainability issues
  • Post findings

Skip gate: does not apply. This is a docs-only PR, but the content is the skill's factual-claims layer that an agent executes against, and the PR is explicitly about correcting prior factual errors — a meaningful review surface, not trivial/automated churn.

Both prior Codex review comments (CHANGELOG version-reuse, and the entrypoint's stale "converge is the only action" summary) already appear resolved in the current head (f605b88): plugin.json is now 0.31.8 with a new ## [0.31.8] changelog section, and SKILL.md/converge.md's preambles now both state that sync can also write committed settings.

Findings

🔴 Important — gotchas.md:53 claims the wrong sentinel value for the subdirectory-install mismatch.

that fleet-state.sh will never match for currentProject: currentProject stays null, so...

Per fleet-state.sh's own currentProject computation (fleet-state.sh#L436-L443), the field is null only when there's no project context to compare against at all (or the scope isn't project/local). Once a project/local record and a resolved project root both exist, a path mismatch evaluates to the boolean false, not null — exactly the case a subdirectory install hits. The existing test suite already pins this for an analogous mismatch: fleet-state.test.sh#L329 asserts "false" for a differently-cased sibling path, while null is reserved for the no-project-context case (:400, :473).

The downstream narrative ("sync's Step 2 never updates it") is unaffected either way, since Step 2 only acts on currentProject: true. But given this PR's own stated bar — testing claims against the real CLI/script rather than wording judgment — this particular sentinel-value claim doesn't hold up against the script it cites. Left an inline comment with a fix link on the exact line.

No other correctness issues found. The rest of the rewritten claims in scope-semantics.md, converge.md, and sync.md (the write-behavior table, the enable -s project merged-effective gating note, and the divergences[] grouping claim in gotchas.md's second paragraph) check out against fleet-state.sh's actual jq logic.
· branch

Comment thread plugins/claude-ops/skills/plugins/context/gotchas.md Outdated
…ot null

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @cursor[bot]'s task in 33s —— View job


Security review

  • Gather context (PR diff since last reviewed head)
  • Apply skip gate
  • Review diff for security issues
  • Post findings

Skip gate: no security-relevant surface. Per the prior review comment, the last security-reviewed head was f605b88; this run covers only the commit since then, up to ad8bfd0 ("docs(claude-ops): subdirectory install yields currentProject false, not null").

That commit changes exactly one line, in one file:

  • plugins/claude-ops/skills/plugins/context/gotchas.md#L53 — corrects the documented sentinel value for a subdirectory-install mismatch from currentProject: null to currentProject: false, matching fleet-state.sh's actual jq logic. This is the fix for the factual error raised in the prior code-review pass on this PR.

No executable code, scripts, or GitHub Actions workflows are touched. No new input handling, no command construction, no credential/secret handling, no CI permission or trigger changes. Purely a one-word prose correction.

No security issues found.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @cursor[bot]'s task in 2m 12s —— View job


Code review complete

  • Gather context (PR diff, changed files, prior review threads)
  • Verify the fixes made since the last review pass
  • Review full diff for remaining correctness/maintainability issues
  • Post findings

Skip gate: does not apply. Docs-only PR, but the content is the skill's factual-claims layer that an agent executes against — a meaningful review surface.

This PR has already been through two rounds of this lane plus Codex, and both real findings from the prior round are now fixed correctly in this head (ad8bfd0):

  1. CHANGELOG/version parityplugin.json is now 0.31.8 with a new ## [0.31.8] section; the previously-published 0.31.7 section is untouched.
  2. gotchas.md sentinel value — the last commit (ad8bfd0) changes "currentProject stays null" to "currentProject stays false". I checked this against fleet-state.sh's actual currentProject computation (fleet-state.sh#L436-L443): once scope is project/local, projectPath is non-empty, and a project root ($cur) is resolved, the ternary always evaluates to a boolean — null is reserved for the no-project-context case. A subdirectory-install mismatch has both a project record and a resolved root, so it's false. The fix is correct.

Also verified the earlier "converge is the only action that can touch committed settings" claims (flagged by the first review round) are now reworded consistently in both SKILL.md and converge.md — both now say sync can also write committed settings via Step 5's enable -s project, and neither claims exclusivity anymore. The scope-semantics.md checkout-root citation (fleet-state.sh:211-221) and the fleet-state.test.sh nested-subdirectory claim both check out against the actual script.

No new findings

No further correctness or maintainability issues in this diff. The write-behavior table, the enable -s project merged-effective-value gating note, and the cwd-vs-checkout-root split are all internally consistent and cross-referenced correctly across scope-semantics.md, sync.md, converge.md, and gotchas.md.
· branch

@kyle-sexton
kyle-sexton merged commit 35d3429 into main Aug 12, 2026
38 checks passed
@kyle-sexton
kyle-sexton deleted the fix/claude-ops-enabledplugins-write-facts branch August 12, 2026 23:22
kyle-sexton added a commit that referenced this pull request Aug 13, 2026
…invariant (#2539)

## Summary

`SKILL.md` states the rule the skill is built around:

> `converge` is the one action that can touch a committed
`.claude/settings.json`, and only after an
> explicit per-plugin confirm.

`sync.md` Step 5 broke it. It issued `claude plugin enable <id> -s
project` for every
`currentProject: true` completeness gap, and 0.31.8 verified on Claude
Code 2.1.228 that this call
writes exactly that file. So the default, headless-capable action
silently modified a team-shared
tracked file. 0.31.8 documented the exposure and asked the report to
name it — a mitigation, not a
fix — and in the interim the invariant itself had been softened in
`SKILL.md` and `converge.md` to
*describe* the violation rather than remove it.

**The fix.** Step 5 now enables automatically only where the write is
not team-shared state:

- **`user`** — machine-scope `~/.claude/settings.json`.
- **`local`** — the gitignored `.claude/settings.local.json` (verified
in #2526).
- **`project`** — never enabled automatically. Reported as an "Action
needed" row carrying the
runnable command, in the `cd`-into-its-own-`projectPath` form
`converge.md` mandates and with the
fully-qualified id `gotchas.md` requires, so acting on it is a copy
rather than a reconstruction.

**Why not confirm instead of skip.** `converge` can afford a confirm
gate because it *aborts* in an
autonomous session — `CLAUDE_CODE_REMOTE`, `/loop`, `/schedule`. `sync`
has no such abort by design;
it is the on-demand and headless maintenance action, and
`/claude-ops:lanes` runs it from scheduled
background sessions. A path that writes team-shared state cannot be made
safe by asking when there
may be nobody to answer. The write must not happen.

After this change no `sync` path writes a committed settings file, so
`SKILL.md` and `converge.md`
state the invariant plainly again instead of carving out `sync`.

## Test plan

- `markdownlint-cli2` on all changed markdown — 0 issues.
- `lychee --offline` — 0 errors.
- `check-skill.sh` on `plugins/claude-ops/skills/plugins` — **PASS**, 0
errors, 0 warnings.
- `scripts/affected-tests.sh --run` — every changed file is a recorded
no-suite class.
- `plugin.json` parses; version 0.31.9 → 0.31.10.
- Consistency sweep: grepped the skill tree for any surviving claim that
`sync` writes committed
settings — none remain. The 0.31.8 "Known exposure" paragraph is gone,
and the two softened
  invariant statements are restored.

## Related

Closes #2535

Completes the remediation deferred in #2526 / #2527, which documented
this exposure but left it
unscheduled. Underlying CLI behavior is recorded upstream in
`anthropics/claude-code#81058`.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

claude-ops plugins skill: enable -s project writes committed settings, and a subdirectory install is invisible to currentProject

2 participants