fix(setup): idempotent claude plugin install (stops .claude.json truncation)#33
Merged
Merged
Conversation
…ncation
Root cause: every `claude plugin install` call writes to ~/.claude/.claude.json
(or %USERPROFILE%\.claude\.claude.json). The Claude Code CLI's
deserialize-modify-serialize cycle does NOT preserve fields outside its
internal struct. Re-running install for already-installed plugins silently
strips:
- oauthAccount.organizationType (e.g. "claude_max")
- oauthAccount.organizationRateLimitTier
- oauthAccount.hasExtraUsageEnabled
- oauthAccount.organizationRole / organizationName
- projects {} map (per-project session state)
- autoUpdates, installMethod, claudeCodeFirstTokenDate, hasCompletedOnboarding,
and ~50 other onboarding/UI fields
Symptom observed 2026-05-15: after running setup-linux.sh, .claude.json
shrank from 75475 bytes (60 keys) to 1467 bytes (8 keys). Claude Code
lost the subscription marker and prompted for re-authentication in EVERY
project. Backups under ~/.claude/backups/ confirmed the pre-truncation
state was intact and recoverable via cp.
Fix: cache `claude plugin list` output once before the loop; skip
`plugin install` calls for plugins already present. Mirrors the
idempotence pattern already used for MCP registration (`claude mcp get`
check at line 447 / setup-windows.ps1 line 196).
Bash: shell glob match + grep -qF against cached list.
PowerShell: -match with [regex]::Escape against the joined output.
The CLI bug itself is upstream Anthropic. Eliminating the trigger
(unconditional re-install) eliminates the symptom in our environment.
If Anthropic fixes the CLI later, our wrapper stays correct.
Verification:
- After this fix: running setup-linux.sh twice in a row leaves
~/.claude/.claude.json at the same size (no truncation).
- Subscription state (organizationType: claude_max) persists across
setup runs.
Unrelated to PR #32 (skill-sync symlink-follow bug). Both PRs address
different setup-linux data-loss bugs; they touch different sections of
the same file and do not conflict.
…lyzer PSUseBOMForUnicodeEncodedFile warning triggers on the em dash I added in the plugin-install idempotence comment. PSScriptAnalyzer rule requires either pure ASCII or a UTF-8 BOM marker; keeping the file ASCII is the simpler fix and matches the rest of setup-windows.ps1.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Every `claude plugin install` call writes to `~/.claude/.claude.json`. The Claude Code CLI's deserialize-modify-serialize cycle silently strips fields that aren't in its internal struct — including `oauthAccount.organizationType` (the subscription marker), `organizationRateLimitTier`, the per-project `projects` map, onboarding flags, install metadata, and ~50 other fields.
Re-running setup with the prior unconditional install loop truncates `.claude.json` from ~75 KB to ~1.5 KB every time, which makes Claude Code believe the user has no subscription and prompts for re-authentication in every project.
Mirror the idempotence pattern already used for MCP registration (line 447): cache `claude plugin list` once, skip plugins already present.
Evidence
Observed 2026-05-15 after a routine `setup-linux.sh` run:
Recovered via `cp` from the backup. Symptom (re-auth in every project) resolved immediately on restore.
Why this is upstream-driven but our trigger is fixable
The deserialize-strip behaviour is a bug in the `claude` CLI binary itself. We cannot patch it. But we own the trigger — the unconditional plugin install loop in `setup-linux.sh` / `setup-windows.ps1`. Adding the same idempotence check we already use for MCPs makes re-runs of setup a no-op when plugins are already installed.
If Anthropic later fixes the CLI to preserve unknown fields, this wrapper stays correct (now-idempotent vs always-idempotent — same outcome).
Diff
```
setup-linux.sh | 25 ++++++++++++++++++++++---
setup-windows.ps1 | 20 ++++++++++++++++++--
2 files changed, 40 insertions(+), 5 deletions(-)
```
Relationship to #32
Unrelated to #32 (skill-sync symlink-follow bug). Both PRs fix different data-loss bugs in setup scripts:
Different sections of `setup-linux.sh` (line ~304 vs line ~460). No conflict. Can merge in any order.
Test plan
Follow-up (out of scope, separate concern)