Skip to content

fix(claude-ops): stop sync writing committed settings, restoring the invariant - #2539

Merged
kyle-sexton merged 3 commits into
mainfrom
fix/sync-never-writes-committed-settings
Aug 13, 2026
Merged

fix(claude-ops): stop sync writing committed settings, restoring the invariant#2539
kyle-sexton merged 3 commits into
mainfrom
fix/sync-never-writes-committed-settings

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

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:

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/pluginsPASS, 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.

@cursor

cursor Bot commented Aug 13, 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.

…invariant

SKILL.md states converge is the one action that may touch a committed
.claude/settings.json, and only behind a per-plugin confirm. Step 5 broke that:
it issued `claude plugin enable <id> -s project` for any currentProject gap, and
0.31.8 verified that call writes exactly that file. 0.31.8 documented the
exposure and asked the report to name it, which is a mitigation, not a fix — and
in the meantime the invariant itself had been softened to describe the violation.

Step 5 now enables automatically only where the write is not team-shared state:
user scope, and local scope, which writes the gitignored settings.local.json. A
project-scope gap becomes an Action-needed row carrying the runnable
cd-into-its-own-projectPath command with a fully-qualified id, so the user runs
it deliberately and reviews the diff.

Confirming was rejected as the alternative. converge can afford a confirm gate
because it aborts outright in an autonomous session; sync is the on-demand and
headless maintenance action with no such abort, so there may be no human to
answer. The write must not happen rather than be authorized.

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

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

@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: c41c73bccb

ℹ️ 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/skills/plugins/context/sync.md
Main shipped its own 0.31.10 while this branch was open.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kyle-sexton
kyle-sexton force-pushed the fix/sync-never-writes-committed-settings branch from c41c73b to df05d59 Compare August 13, 2026 00:24
@claude

claude Bot commented Aug 13, 2026

Copy link
Copy Markdown

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


Security review

Scope: /review:security-review — logic, trust-boundary, and Actions security findings only (docs/config, no code diffs).

Result: No security issues found

This PR is doc-only (SKILL.md, converge.md, scope-semantics.md, sync.md, CHANGELOG.md, plugin.json version bump) and its entire purpose is to tighten a trust boundary: it stops sync — the on-demand/headless action with no autonomous-session abort — from ever calling claude plugin enable <id> -s project, the one call that writes a team-shared, git-tracked .claude/settings.json (confirmed by scope-semantics.md's empirically-verified call table at scope-semantics.md#L59-L84).

Checked specifically:

  • Grepped every -s project occurrence across sync.md/converge.md/SKILL.md. The only -s project call sync still auto-executes is plugin update (sync.md#L92), which the same doc's own verified table confirms does not write committed settings. The enable -s project command at sync.md#L194 is emitted as inert report text for a human to run deliberately, never executed by the skill itself — so the write-without-confirmation path is genuinely closed.
  • converge's autonomous-session abort gate (converge.md#L8-L15) — the only remaining path that can touch committed settings — is unmodified by this PR and still fails closed when session-type is ambiguous.
  • No shell-injection surface: the only unexecuted placeholder command ((cd "<projectPath>" && claude plugin enable <id>@<marketplace> -s project)) is presented as documentation text, not built into anything this skill runs, and projectPath/<id> originate from the local installed_plugins.json record (same-machine install state), not attacker-influenced input.
  • No workflow/permissions changes, no secrets handling, no dependency/action pins touched.

Note (out of scope for this lane): Codex's review already flagged a real logic bug at sync.md#L182-L199 — for an id installed at both user and project scope with no raw entry in either, Step 5 auto-enables user scope first, which (per scope-semantics.md#L82-L84) makes the merged effective value true, so the reported enable -s project remediation command will then fail with "already enabled at project scope" instead of writing the project entry. I confirmed this against the doc's own stated CLI semantics and it's accurate — but it fails safe (no unauthorized write occurs; the committed file is simply never updated), so it's a correctness/usability issue for /review:code-review, not a security finding for this lane.

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

Last security-reviewed head: 78a2cf77cc21cb83748286973afa1b8e5162b1f3. 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 13, 2026

Copy link
Copy Markdown

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


Code review complete

  • Gather context (PR diff, changed files, custom instructions)
  • Run /review:code-review criteria against the diff
  • Investigate Codex's P2 finding on sync.md:194 (dual-scope remediation command)
  • Post findings (inline + summary)

Scope: reviewed the 6 changed files (plugin.json, CHANGELOG.md, SKILL.md, converge.md, scope-semantics.md, sync.md) against origin/main per the code-review lane's criteria (correctness + maintainability, security out of scope for this lane).

Finding posted inline

One correctness issue, posted as an inline comment on sync.md:182-194:

Independently confirmed Codex's P2 flag on the same lines. For a plugin id installed at both user and project scope with no raw enabledPlugins entry in either, Step 5 auto-enables the user row first, then reports the project row's enable -s project command. Per this PR's own scope-semantics.md:82-84, enable -s project gates on the merged effective value and fails once the id is already true at user scope — so the reported "runnable" remediation command is guaranteed to fail exactly in this dual-scope case, contradicting the PR's stated goal that the row is "a copy rather than a reconstruction."

Everything else checked out

  • The core fix is sound: Step 5's user/local auto-enable vs. project report-only split correctly stops sync from writing team-shared state, matching the empirical CLI facts already recorded in scope-semantics.md (enable -s project writes the committed file; -s user/-s local do not).
  • converge.md and SKILL.md's invariant language is restored to state plainly that converge is the only action touching committed settings — verified no other file in the skill tree still claims sync can write one (grepped for "writes committed"/"write committed"/"Known exposure").
  • The project-scope report row uses the fully-qualified <id>@<marketplace> form and the cd-into-projectPath form, consistent with gotchas.md's fully-qualified-id requirement and converge.md's -s project-has-no-path-flag rationale — good, except for the dual-scope ordering bug above.
  • plugin.json version bump (0.31.10 → 0.31.11) matches the new CHANGELOG.md entry.
  • Note: the PR body's test-plan items (markdownlint, lychee, check-skill.sh, affected-tests.sh) are author-claimed; I did not independently re-run them.

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

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

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

…h would break

Review found the reported remediation can fail by construction. An id with no
enabledPlugins entry anywhere but install records at both user and project scope
yields two rows in one run: the user row enables first, and `enable -s project`
gates on the merged effective value rather than that scope's raw map, so the
command the project row hands the user then fails with "already enabled at
project scope" — the opposite of the runnable-command goal.

Step 5 now emits the project row only for ids it did not enable at user or local
scope in the same run. Skipping is correct, not a convenience: after the user
enable the plugin already loads in that project by scope precedence, so nothing
is broken; only the team-shared declaration is absent, and that is the user's
call rather than drift for sync to report as actionable.

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

claude Bot commented Aug 13, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 19s —— View job


Security review

Scope: /review:security-review — logic, trust-boundary, and Actions security findings only (docs/config, no code diffs). Reviewed at HEAD 78a2cf7.

Result: No security issues found

This PR remains doc-only (SKILL.md, converge.md, scope-semantics.md, sync.md, CHANGELOG.md, plugin.json version bump), and its purpose continues to be tightening a trust boundary: sync never calls claude plugin enable <id> -s project — the one call that writes a team-shared, git-tracked .claude/settings.json.

The prior review of this PR (posted at head df05d59) found no security issues. Since then, one new commit was pushed — 78a2cf7 ("suppress the project-scope enable row the user branch would break") — which I reviewed specifically:

  • It only touches sync.md and SKILL.md, adding prose guidance that suppresses the "Action needed" project-scope report row when the same run already auto-enabled the id at user/local scope (this is the fix for the dual-scope ordering bug both Codex and the prior code-review pass flagged).
  • The change is purely descriptive/logic guidance consumed by the skill's LLM-driven steps — no new command is introduced, no existing command becomes auto-executed, and the -s project write path remains report-only, never run by the skill itself.
  • No workflow, permissions, secrets, or dependency-pin changes.

Confirms the write-without-confirmation path stays closed, and this incremental commit doesn't introduce any new security-relevant surface.

Note (out of scope for this lane): the correctness fix itself (suppressing the project row) is a /review:code-review concern, already addressed by the commit — not re-litigated here since it fails safe either way (no unauthorized write in either case).

@kyle-sexton
kyle-sexton merged commit aa9085c into main Aug 13, 2026
37 of 38 checks passed
@kyle-sexton
kyle-sexton deleted the fix/sync-never-writes-committed-settings branch August 13, 2026 01:04
@claude

claude Bot commented Aug 13, 2026

Copy link
Copy Markdown

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


Code review complete

  • Gather context (PR diff, changed files, custom instructions)
  • Invoke /review:code-review criteria against the diff
  • Verify the previously-flagged P2 (dual-scope remediation command) against the current HEAD
  • Post findings

Scope: re-reviewed all 6 changed files at HEAD 78a2cf7 against origin/main's prior state, focused on correctness + maintainability (security already covered by a separate lane on this PR).

Result: no outstanding findings

This push resolves the one issue raised in earlier review rounds. Both Codex and my own prior pass flagged that sync.md could report a project-scope enable -s project remediation command that was guaranteed to fail — for an id installed at both user and project scope with no raw enabledPlugins entry anywhere, Step 5's user-scope auto-enable flips the merged effective value, so a subsequently reported -s project command would error with "already enabled at project scope."

The current diff adds exactly the fix that gap needed, at sync.md#L202-L213:

Order matters — suppress this row for any id the user/local branch just enabled. ... Emit the project row only for an id this step did not enable at user or local scope

I verified this against scope-semantics.md#L82-L84 (the merged-effective-gating fact both reviews cited) and it correctly covers both branches that can flip the merged value — user and local — not just user, so the local+project dual-install case is covered too.

Rest of the sweep:

  • Grepped the whole skill tree for writes committed/write committed/Known exposure — no stale claim that sync can touch a committed file survives (grep output).
  • SKILL.md's report format (SKILL.md#L108-L122) and sync.md Step 5/6 agree on when a project-scope row appears and its exact command shape.
  • converge.md's restored "only action that can rewrite committed settings" language is consistent with the rest of the tree, and its own logic is untouched by this PR.
  • gotchas.md references (fully-qualified id, CLAUDE_PROJECT_DIR resolution) check out against the actual file content.
  • plugin.json version (0.31.11) matches the new CHANGELOG.md head entry.

No inline comments filed — nothing new to flag.

cursor Bot pushed a commit that referenced this pull request Aug 13, 2026
Rebase onto main (which shipped sync fix as 0.31.11) and carry the
morning-brief taxonomy fix as 0.31.12.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
kyle-sexton added a commit that referenced this pull request Aug 13, 2026
…are degradation (#2540)

## Summary

The morning-brief Queues section no longer hardcodes the
melodic-software queue label taxonomy on live runs. Default labels are
unchanged, but the script now filters to labels that actually exist in
the target repo (same degrade-when-absent pattern as the telemetry-issue
path). Consuming repos with a different labeling scheme no longer see
misleading `0`/`?` count rows.

## Changes

- Live queue counts filter against `gh label list` (or
`--repo-labels-json` in tests)
- Degrade to "no queue labels found" when none of the configured
defaults exist
- Add `--queue-labels` (comma-separated pin) and `--decision-label`
flags
- Parked-decisions section degrades when the decision label is absent
- Bump `claude-ops` to **0.31.12** (main already shipped 0.31.11 via
#2539)
- Regression tests for degradation, partial overlap, and pinned labels

## Test plan

- [x] `bash
plugins/claude-ops/skills/morning-brief/morning-brief.test.sh` (81
cases)

## Related

No linked issue

Fixes #610

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
kyle-sexton added a commit that referenced this pull request Aug 14, 2026
…de (#2613)

No linked issue

## Summary

Re-verifies `docs/CLOUD-SESSIONS.md` against rung-1 raw-markdown fetches
of the three live cloud pages (`web-quickstart`,
`claude-code-on-the-web`, `cloud-environments`) and adds
`docs/CLOUD-FLEET-SETUP.md`, a goal-oriented guide for getting every
melodic-software repository runnable in Claude Code cloud sessions from
one shared environment.

## Fix

- `docs/CLOUD-SESSIONS.md`: folds in the new `web-quickstart` page
(onboarding split out of `claude-code-on-the-web`, including the
`/web-setup` terminal flow), records the roughly-five-minute
setup-script cache-build budget and its bearing on the guarded bootstrap
one-liner, and notes Claude Tag as an environment-sharing surface plus
self-hosted environments as the routed alternative. Verification stamp
scoped honestly: the three cloud pages carry 2026-08-13; other linked
pages keep 2026-07-30.
- `docs/CLOUD-FLEET-SETUP.md` (new): fleet audit of all 13 attachable
active repos (toolchain pins: .NET SDK 10.0.302/10.0.400, Node 24.18.0,
Python 3.14, Go 1.26.6, pwsh), a shared-environment design with a
paste-ready setup script, manifest-driven per-repo SessionStart hook +
`settings.json` templates, routine starters, a post-creation
verification checklist, and network-allowlist fallbacks for the .NET and
Python installers. Records the finding that this repo's
`session-start.sh` is present but unregistered in committed
`settings.json` (contradicting CLOUD-SESSIONS.md), left to an explicit
decision because claude-ops converge owns committed plugin enablement
(#2539).

## Verification

- All three cloud pages fetched via the raw `.md` channel per the
upstream-drift convention (slugs confirmed canonical in `llms.txt`,
first headings checked); every existing CLOUD-SESSIONS.md anchor and
restated claim matched the live text before the three deltas were folded
in.
- Fleet audit derived from shallow clones of each repo's default branch
on 2026-08-13.
- `npx markdownlint-cli2` clean on both files; the embedded hook
template passes `shellcheck` (one expected SC1091 info for the sourced
nvm.sh).

## Related

- #2539 (claude-ops converge owns committed settings — why the
unregistered-hook finding is reported, not patched)
- `docs/conventions/upstream-drift/README.md` (fetch-route and stamp
discipline followed)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01AXaqhrFZeGTzbCJun12Ngx

---
_Generated by [Claude
Code](https://claude.ai/code/session_01AXaqhrFZeGTzbCJun12Ngx)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Aug 15, 2026
…ste-once rollout kit (#2677)

No linked issue

## Summary

Cloud sessions could never use this repo's plugins on turn one: the
SessionStart hook installs the full catalog, but Claude Code builds its
plugin/command registry at process start — before hook effects land —
and never re-reads it. This PR records that finding with its evidence
and fix, and adds the copy-paste rollout kit for moving the whole fleet
(and every account environment) onto the layout that works.

## Fix

- `docs/CLOUD-SESSIONS.md`: document the same-session limit observed
2026-08-15 (hook completed `65 enabled, 65 newly installed, 0 failed`
with the on-disk registry fully populated, yet the same session resolved
no plugin command or skill), plus the confirmation that a stop/resume
restarts the process and loads everything; reframe the environment
setup-script one-liner from a performance lever to the required
pre-launch slot for plugin liveness.
- `.claude/hooks/session-start.sh`: state the timing limit in the header
— hook-time installs serve the *next* process start, so the environment
setup script must run this bootstrap at cache build for a session to
start with plugins loaded.
- `docs/CLOUD-FLEET-SETUP.md`: refresh the stale "bootstrap is currently
unwired" finding (settings.json registers the hook on `main` and it
ran), fix the verification step that relied on `/plugin` (unavailable in
cloud sessions), and point step 1 at the rollout kit.
- `prompts/cloud-bootstrap-rollout.md` (new): the paste-once account
environment stub, the prompt for creating the shared
`cloud/env-setup.sh` in `melodic-software/standards`, and the per-repo
migration prompt that renames `.claude/hooks/session-start.sh` to
`.claude/cloud-bootstrap.sh` — one script, two callers (environment
setup script pre-launch; SessionStart hook for per-session drift repair)
— and settles on one Default environment per account.

## Verification

- `markdownlint-cli2`, `typos`, and `editorconfig-checker` pass on all
touched files; `shellcheck` and `shfmt -d` pass on the hook
(comment-only change).
- Empirical basis verified live in the cloud session that produced this
PR: installed-on-disk vs empty session registry, a failed mid-session
skill probe, and full catalog loading after resume (this repo's own
commit-convention hook began enforcing mid-session once loaded).
- Official-docs basis: hooks reference documents no same-session pickup;
`/plugin` and `--plugin-dir` are unavailable in cloud sessions; the
setup script runs after clone and before the session process.

## Related

`docs/CLOUD-SESSIONS.md` §"Plugins in sessions on this repo",
`docs/CLOUD-FLEET-SETUP.md`, `docs/MIGRATION-PLAYBOOK.md` (same-version
commit drift), #2061, #2539, #2657

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_018NhKrvZ4Jz1qK2wWDWXiZd

---
_Generated by [Claude
Code](https://claude.ai/code/session_018NhKrvZ4Jz1qK2wWDWXiZd)_

---------

Co-authored-by: Claude <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 sync Step 5 writes committed settings, violating the skill's own only-converge-touches-committed-settings invariant

1 participant