Summary
When /claude-ops:plugins sync installs new catalog plugins (Step 4), Claude Code's own settings writer appends each new enabledPlugins key to the end of the map rather than inserting it in alphabetical position. The rest of the map is alphabetically sorted, so every sync that installs something leaves a visibly unsorted tail that never self-heals.
Observed
Machine: Windows 11, Claude Code 2.1.240, claude-ops 0.36.1, install_new: all.
A sync run installed five plugins at user scope. ~/.claude/settings.json afterwards:
"wizard@melodic-software": false,
"work-items@melodic-software": false,
"x@melodic-software": false,
"ai-slop@melodic-software": false,
"context-budget@melodic-software": true,
"coupling@melodic-software": false,
"improvement@melodic-software": false,
"overengineering@melodic-software": false
},
The first ~70 entries are sorted; the five newly installed ones are appended after x@melodic-software.
Why it matters
- The map is long enough (70+ entries) that scanning for a specific plugin depends on the sort order holding.
- The tail is cumulative: every future sync that installs anything appends another unsorted block, so the file degrades monotonically.
- For anyone whose
~/.claude/settings.json is managed (chezmoi, dotfiles repo, or any config-in-git setup), the append churns diffs in a way an ordered map would not.
Proposed change
After Step 4 installs anything, sync should normalize the enabledPlugins key order alphabetically for the scope it just wrote, and report that it did so.
Constraint to resolve first
This is not a free change — it collides with an existing invariant in the skill:
- SKILL.md's Scope section states that
sync "never writes a committed .claude/settings.json", and sync.md Step 5 reinforces it: converge is the only action permitted to touch committed settings, and only behind a confirm gate.
- There is no
claude plugin verb that reorders enabledPlugins. Sorting means a direct write to a settings file, which every step of sync currently routes around by design.
Suggested scoping that keeps the invariant intact:
- Apply the normalization only at
user scope (~/.claude/settings.json). That file is machine-scope, not team-shared, and Step 5 already writes it automatically via claude plugin enable -s user, so a key reorder there is consistent with what the step is already permitted to do.
- Never normalize
project scope — that is the committed, team-shared file the invariant protects. If a project-scope map is unsorted, report it, do not fix it.
- Make the write a strict reorder: keys and values byte-identical, order alone changed. If a semantic diff appears, abort and report rather than write.
- Emit a report row when normalization runs, so the settings-file write is never silent.
Alternative worth considering
The append behavior originates in Claude Code's settings writer, not in this plugin. An upstream issue asking claude plugin install to insert in sorted position would fix it for every consumer and make the workaround above unnecessary. That may be the better long-term home for the fix; the local normalization is the version that works today.
Environment note
On the machine where this was observed, the settings-file write could not be performed manually from the agent session — the auto-mode classifier denied both the Edit on ~/.claude/settings.json and a gh label list call. Any implementation should assume the write may require an explicit permission grant or a non-auto session, and should fail loudly rather than silently skipping when denied.
Summary
When
/claude-ops:plugins syncinstalls new catalog plugins (Step 4), Claude Code's own settings writer appends each newenabledPluginskey to the end of the map rather than inserting it in alphabetical position. The rest of the map is alphabetically sorted, so every sync that installs something leaves a visibly unsorted tail that never self-heals.Observed
Machine: Windows 11, Claude Code 2.1.240, claude-ops 0.36.1,
install_new: all.A sync run installed five plugins at user scope.
~/.claude/settings.jsonafterwards:The first ~70 entries are sorted; the five newly installed ones are appended after
x@melodic-software.Why it matters
~/.claude/settings.jsonis managed (chezmoi, dotfiles repo, or any config-in-git setup), the append churns diffs in a way an ordered map would not.Proposed change
After Step 4 installs anything,
syncshould normalize theenabledPluginskey order alphabetically for the scope it just wrote, and report that it did so.Constraint to resolve first
This is not a free change — it collides with an existing invariant in the skill:
sync"never writes a committed.claude/settings.json", and sync.md Step 5 reinforces it:convergeis the only action permitted to touch committed settings, and only behind a confirm gate.claude pluginverb that reordersenabledPlugins. Sorting means a direct write to a settings file, which every step ofsynccurrently routes around by design.Suggested scoping that keeps the invariant intact:
userscope (~/.claude/settings.json). That file is machine-scope, not team-shared, and Step 5 already writes it automatically viaclaude plugin enable -s user, so a key reorder there is consistent with what the step is already permitted to do.projectscope — that is the committed, team-shared file the invariant protects. If a project-scope map is unsorted, report it, do not fix it.Alternative worth considering
The append behavior originates in Claude Code's settings writer, not in this plugin. An upstream issue asking
claude plugin installto insert in sorted position would fix it for every consumer and make the workaround above unnecessary. That may be the better long-term home for the fix; the local normalization is the version that works today.Environment note
On the machine where this was observed, the settings-file write could not be performed manually from the agent session — the auto-mode classifier denied both the
Editon~/.claude/settings.jsonand agh label listcall. Any implementation should assume the write may require an explicit permission grant or a non-auto session, and should fail loudly rather than silently skipping when denied.