Plugin install/update writes ~/.claude/plugins/npm-cache/package.json without truncating, leaving trailing bytes from previous content
Summary
\~/.claude/plugins/npm-cache/package.json\ ends up with valid JSON followed by leftover bytes from a previous write. Subsequent \claude plugin update\/\install\ then fails with \EJSONPARSE\ and the user is locked out of plugin updates until the file is manually repaired.
Error
\\\\
✘ Failed to update plugin "<plugin>@<marketplace>": Failed to install npm package: npm error code EJSONPARSE
npm error JSON.parse Invalid package.json: JSONParseError: Unexpected non-whitespace character after JSON at position 114 (line 7 column 3) while parsing near "...: \"^X.Y.Z\"\n }\n}\n }\n}\n"
\\
Evidence
\xxd ~/.claude/plugins/npm-cache/package.json\ on the affected machine:
\\\\
00000000: 7b0a 2020 2264 6570 656e 6465 6e63 6965 {. "dependencie
00000010: 7322 3a20 7b0a 2020 2020 2240 7363 6f70 s": {. "@scop
00000020: 652f 706c 7567 696e 2d61 223a 2022 5e58 e/plugin-a": "^X
00000030: 2e59 2e5a 222c 0a20 2020 2022 4073 636f .Y.Z",. "@sco
00000040: 7065 2f70 6c75 6769 6e2d 6222 3a20 225e pe/plugin-b": "^
00000050: 582e 592e 5a22 0a20 207d 0a7d 0a20 207d X.Y.Z". }.}. }
00000060: 0a7d 0a .}.
\\
Trailing bytes \ }\n}\n\ after the closing \}\n\ are leftovers from a previous, longer JSON document. The current intended JSON is shorter than the file on disk.
Root cause analysis
- \
~/.claude/plugins/npm-cache/package-lock.json\lists a different dependency set than \package.json\does, indicating a recent plugin operation changed the dependency set. - Old content size > new content size.
- Writer overwrote starting at offset 0 but did not truncate, leaving the suffix of the previous content intact past the end of the new content.
Consistent with writing via \fs.writeFile\/\writeFileSync\ without \flag: 'w'\ semantics, or opening with \O_WRONLY | O_CREAT\ without \O_TRUNC\, or using \'r+'\/\'a'\ flags.
Repro hypothesis
- Install plugins A + B (writes a longer package.json).
- Switch to plugins B + C, where the new package.json is shorter in bytes.
- On-disk file now contains the new JSON followed by the tail of the old JSON.
- Next \
claude plugin install/update/list\reads the file via npm, fails with EJSONPARSE.
Workaround
Manually rewrite the file with valid JSON:
\\\bash\
cat > ~/.claude/plugins/npm-cache/package.json <<'JSONEOF'
{
"dependencies": {
"@scope/plugin-b": "^X.Y.Z",
"@scope/plugin-c": "^X.Y.Z"
}
}
JSONEOF
\\
Plugin operations recover after this.
Suggested fix
When writing \~/.claude/plugins/npm-cache/package.json\, ensure the write truncates the file (e.g., \fs.writeFileSync(path, content, { flag: 'w' })\, or write to a tempfile and atomically \rename\ over the target).
Environment
- Claude Code: 2.1.140
- Node: v22.13.1
- OS: macOS (Darwin 25.4.0)
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗