Skip to content

fix(setup): idempotent claude plugin install (stops .claude.json truncation)#33

Merged
mlorentedev merged 3 commits into
mainfrom
fix/setup-claude-plugin-idempotence
May 16, 2026
Merged

fix(setup): idempotent claude plugin install (stops .claude.json truncation)#33
mlorentedev merged 3 commits into
mainfrom
fix/setup-claude-plugin-idempotence

Conversation

@mlorentedev
Copy link
Copy Markdown
Owner

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:

File Size Keys `organizationType`
Live `.claude.json` post-setup 1467 B 8 (missing)
Pre-setup backup at `~/.claude/backups/.claude.json.backup.1778900735198` 75475 B 60 `claude_max`

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(-)
```

  • bash: cache `installed_plugins=$(claude plugin list 2>/dev/null || true)` once, then `grep -qF` per loop iteration.
  • PowerShell: cache `$installedPlugins` once via `-join`, then `-match [regex]::Escape(...)` per iteration.
  • Counts and final log line ("`N added, M already present`") match the MCP block style.

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

  • Run `./setup-linux.sh` once → `~/.claude/.claude.json` size stays at restored ~75 KB
  • Run `./setup-linux.sh` twice in a row → second run logs `"12 already present, 0 added"` and file size unchanged
  • `jq '.oauthAccount.organizationType' ~/.claude/.claude.json` returns `"claude_max"` after both runs
  • Open Claude Code in any project → no re-auth prompt
  • Equivalent verification on Windows via `setup-windows.ps1`

Follow-up (out of scope, separate concern)

  • Defensive monitor: add a check to `vault-health.sh` or `claude-session-start.sh` that flags if `~/.claude/.claude.json` size drops below a threshold (e.g. 10 KB). Catches recurrence even if a different trigger fires.
  • Upstream report: file an issue with Anthropic re: `claude plugin install` not preserving unknown fields on rewrite of `.claude.json`. (Out of scope for dotfiles.)

…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.
@mlorentedev mlorentedev merged commit 6420804 into main May 16, 2026
5 checks passed
@mlorentedev mlorentedev deleted the fix/setup-claude-plugin-idempotence branch May 16, 2026 03:23
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.

1 participant