fix(setup): wrap claude plugin install with .claude.json snapshot/restore guard (BUG-004)#57
Merged
Conversation
…tore guard (BUG-004) Defends against upstream anthropics/claude-code#59870: every `claude plugin install` call writes ~/.claude/.claude.json via a deserialize-modify-serialize cycle that silently drops subscription metadata (organizationType, organizationRateLimitTier, projects map, onboarding flags), shrinking the file from ~75 KB to ~1.5 KB and forcing re-authentication in every project. The existing idempotence guard (`grep -qF` bash / `-match [regex]::Escape` PS) yields a false negative for claude-mem@thedotmack because it does not appear in `claude plugin list` output (different marketplace, @thedotmack vs @claude-plugins-official), so every setup run triggers one real install of claude-mem and hits the upstream bug. dotfiles#33 was the original trigger fix (idempotence guard) but this case slipped through; SDD-021 (PR #54) added the session-start size canary as a detector. This PR is the third layer: prevention. Implementation: - setup-windows.ps1: `Backup-AndRestoreClaudeJson -Action { ... }` snapshots to `[System.IO.Path]::GetTempFileName()` before the action, restores via Copy-Item in `finally` iff pre-size >= 10240 AND post-size < pre/2. - setup-linux.sh: `snapshot_claude_json` (echoes tempfile path) + `restore_claude_json_if_truncated` pair around the bash `claude plugin install` call. Same heuristic. - Both citations of #59870, #33, and SDD-021 in the helper headers for traceability. - 10240 byte floor reused from SDD-021's canary -- single SSOT for "anomalously small .claude.json". Verification: - Synthetic smoke (pwsh): 52060 -> 2 bytes -> wrapper detected and restored to 52060 with [WARNING] line citing #59870. - Sub-threshold smoke: 52060 -> 28633 (55% of pre) -> no restoration (above 50% floor), as expected. - Real smoke x2 on Windows admin machine: 52055 bytes preserved across runs. Upstream bug did not fire in vivo this afternoon (intermittent), so the wrapper's in-vivo behavior under the real bug is not demonstrated this session; synthetic test proves the logic. - 6 new bats asserts in tests/setup-windows.bats + 6 in tests/setup-linux.bats (including 2 cross-OS parity tests). Verified red->green via grep-by-grep emulation; full bats run in CI. - PSScriptAnalyzer clean on changes; bash -n setup-linux.sh clean. Closes residual trigger from dotfiles#33; complements SDD-021 size monitor. Spec: specs/BUG-004-claude-mem-truncate-guard/ Vault: 10_projects/dotfiles/11-tasks.md (entry added) Lesson: 10_projects/dotfiles/90-lessons.md "Defensive monitors are not fixes" Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Merged
6 tasks
mlorentedev
added a commit
that referenced
this pull request
May 19, 2026
…werShell 5.1 (BUG-005) SDD-002 (PR #51) introduced Merge-ClaudeSettings which uses `ConvertFrom-Json -AsHashtable` -- a parameter added in PowerShell 7.0 that does NOT exist in Windows PowerShell 5.1. The natural Windows command `PowerShell -ExecutionPolicy Bypass -File .\setup-windows.ps1` resolves `PowerShell` to 5.1 on a stock Windows machine, the Merge function's wide try/catch swallows the ParameterBindingException as if it were a JSON parse error ("not valid JSON after placeholder substitution: A parameter cannot be found that matches parameter name 'AsHashtable'"), and the per-key merge from ai/claude/settings.json is silently skipped. Existing ~/.claude/settings.json survives untouched, so the bug is invisible until a fresh-machine bootstrap depends on the template merge. Implementation: preamble block after param() and before CONFIGURATION header detects $PSVersionTable.PSVersion.Major < 7. If `Get-Command pwsh` resolves to PowerShell 7+, re-exec under it with `-NoProfile -ExecutionPolicy Bypass -File $PSCommandPath @args` and exit with the child's exit code. Otherwise, print an actionable [ERROR] line pointing at `winget install Microsoft.PowerShell` and exit 1 (fail-loud, no silent skip). Verification: - Smoke under Windows PowerShell 5.1.22621.6931: first output line is `[INFO] Windows PowerShell 5.1.22621.6931 detected; re-executing under pwsh (...) for full feature compatibility (BUG-005)`. Setup proceeds under pwsh, settings.json merged correctly (1687 bytes, 14 plugins + preserved user customizations), zero AsHashtable warnings. - Smoke under pwsh 7.6.1 direct: preamble no-op, no `[INFO] ... detected` lines, existing behavior preserved (verified during BUG-004 smoke same day). - pwsh-missing branch: not directly testable (pwsh installed on dev machine); structurally locked by bats grep asserting `winget install Microsoft.PowerShell` literal + `exit 1`. - 5 new bats asserts in tests/setup-windows.bats; RED -> GREEN via grep-by-grep emulation. - PSScriptAnalyzer: 4 new Write-Host warnings, style-consistent with existing Write-Info/Success/Warn/Err wrappers (those wrappers are themselves Write-Host calls; preamble cannot use them because the wrappers are defined below the preamble's runtime position). Zero Errors. Zero new rule classes. Linux side (setup-linux.sh) is immune by construction (bash + jq, no shell-version coupling); zero changes there. Negative-parity bats assert locks this in. Spec: specs/BUG-005-setup-ps7-reexec/ Vault: 10_projects/dotfiles/11-tasks.md (entry added in same session) Sibling: PR #57 (BUG-004 claude-mem-truncate-guard) Upstream: https://learn.microsoft.com/powershell/scripting/whats-new/what-s-new-in-powershell-70 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced May 19, 2026
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
anthropics/claude-code#59870(the.claude.jsontruncation that drops subscription metadata on everyclaude plugin install).setup-windows.ps1(Backup-AndRestoreClaudeJson) andsetup-linux.sh(snapshot_claude_json+restore_claude_json_if_truncated). Heuristic: restore iff pre-size ≥ 10 KB AND post-size < pre/2. Same10240floor as SDD-021's session-start canary (single SSOT).grep -qF/-match [regex]::Escapeidempotence guard againstclaude plugin listoutput is preserved. The wrapper catches the residual case where the listing omits an installed plugin (e.g.claude-mem@thedotmack, which is from a different marketplace and never appears inclaude plugin list).Root cause
The original trigger fix from
dotfiles#33assumedclaude plugin listenumerates every installed plugin literally. It does not enumerate plugins from third-party marketplaces (@thedotmack). Every setup run therefore attempts a real install ofclaude-mem@thedotmack, which hits the upstream bug and truncates~/.claude/.claude.jsonfrom ~75 KB to ~1.5 KB. Empirically reproduced 2026-05-19: setup outputClaude Code plugins ready (1 added, 11 already present)-> file at 3444 bytes -> re-login prompt in every project. Restored manually from~/.claude/backups/.claude.json.backup.*.SDD-021 (PR #54) added a 10 KB canary in
claude-session-start.{sh,ps1}that surfaced the recurrence — confirming the monitor works but also that the trigger fix was incomplete. This PR is the third layer (prevention).Three-layer model after this PR
claude plugin list. Catches the common case.Test plan
tests/setup-windows.bats(helper defined, #59870 cited, 10240 floor, wrapper precedes install, idempotence preserved)tests/setup-linux.batsincluding 2 cross-OS parity tests[WARNING]line citing #59870.claude.jsonpreserved at 52055 bytesbash -n setup-linux.shcleanCaveats (honest reporting)
#59870is intermittent. Reproduced once this morning, then could not be reproduced in vivo for the rest of the afternoon despite multiple setup runs. The wrapper's in-vivo behavior under the real bug is therefore demonstrated only synthetically in this session. The synthetic test exercises the same code path the real bug would trigger; structural symmetry between the wrapper logic and the upstream failure mode is documented inverification.md.-AsHashtableincompatibility inMerge-ClaudeSettings) is the sibling spec; separate atomic PR.References
specs/BUG-004-claude-mem-truncate-guard/dotfiles#3310_projects/dotfiles/11-tasks.md"BUG-004-claude-mem-truncate-guard"10_projects/dotfiles/90-lessons.md2026-05-19 entry "Defensive monitors are not fixes"