Desktop app silently ignores ~/.claude/settings.json when the file has a UTF-8 BOM (JSON.parse SyntaxError, warn-level log only)
Summary
If ~/.claude/settings.json is saved as UTF-8 with a BOM, the desktop app fails to parse it and falls back to defaults for every user setting — silently. The only trace is a single warn line per read in main.log. Nothing surfaces in the UI, and /config keeps showing (and writing) values as if the file were being honoured.
What makes this particularly hard to notice is the asymmetry: the bundled CLI tolerates the BOM and reads the same file fine, so hooks and permissions from settings.json keep working normally. Only the app-side settings quietly stop applying.
On Windows a BOM is easy to introduce by accident — PowerShell 5.1's Out-File and Set-Content -Encoding utf8 both write UTF-8 with BOM, so any script or one-liner that edits settings.json corrupts it from the app's point of view.
Environment
- Claude Desktop app on Windows 11 Pro (10.0.26200)
- Bundled Claude Code 2.1.219
~/.claude/settings.jsonwritten at some point by a PowerShell script
Evidence
The file, as it was:
$ head -c 16 ~/.claude/settings.json | xxd
00000000: efbb bf7b 0d0a 2020 2020 2265 6e61 626c ...{.. "enabl
JSON.parse rejects the leading U+FEFF, and the app logs:
2026-07-17 02:51:35 [warn] [SettingsIo] Failed to read C:\Users\owner\.claude\settings.json (SyntaxError)
This repeated on every settings read for nine days without any user-visible symptom — 514 occurrences in a single rotated log file:
$ grep -c "SettingsIo] Failed to read" main2.log
514
Meanwhile the CLI parsed the very same file correctly throughout: the SessionStart hook defined only in that settings.json executed normally on every session start.
Stripping the three leading bytes fixed it, with no other change to the file:
before: efbbbf size 3913
after : 7b0d0a size 3910
Impact
Every app-side setting silently reverts to its default. In our case "remoteControlAtStartup": true had been set for weeks and was never actually applied — Remote Control simply did not arm at startup, and there was no way to tell from the UI that the setting was being ignored. theme, inputNeededNotifEnabled and agentPushNotifEnabled were equally inert.
Because /config reads and writes through the same broken path, toggling a setting appears to work and then has no effect, which sends users looking for a bug in the feature rather than in settings loading.
Reproduction
powershell -c "Get-Content ~/.claude/settings.json | Out-File ~/.claude/settings.json -Encoding utf8"(PS 5.1 — adds a BOM)- Restart the app, or just switch session focus.
main.logshows[SettingsIo] Failed to read … (SyntaxError); all app-side settings are now at their defaults, with no UI indication.
Expected
- Strip a leading BOM before
JSON.parseinSettingsIo(the CLI already does the equivalent — the two readers should agree on what a valid settings file is). - If a settings file cannot be parsed, surface it to the user (a banner, or an entry in
/doctor) instead of a singlewarnline in a log the user has no reason to open. Silently running on defaults is the worst outcome here.