You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
One wrong-typed field in a single profiles.json/commands.json entry discards every stored entry, and the next save erases them #168
#125 and #133 made loading tolerate one bad entry: ToProfile (Keybinding/Services/JsonKeybindingRepository.cs:126-160) and ToCommand (228-244) skip an entry whose values fail validation (ArgumentException). But that per-entry handling only runs after the whole file has deserialized successfully:
LoadAllProfilesInternalAsync (JsonKeybindingRepository.cs:98-119) calls JsonSerializer.Deserialize<List<ProfileDto>> on the whole file and catches JsonException by returning [].
LoadCommandsAsync (199-221) does the same for commands.json.
A JSON type mismatch inside any one element ("name": 5, "notes": "CTRL" instead of an array, "notes": [5], "category": 5) makes System.Text.Json throw JsonException for the whole list, so every entry is dropped, not just the bad one. SaveProfileAsync (43-72) then reads the file back the same way, gets [], and writes only what's in memory.
Failure scenario (reproduced with a scratch MSTest)
Save profiles a (with file.save bound to Ctrl+S) and b.
Hand-edit profiles.json so only profile b has "name": 5.
New KeybindingManager → InitializeAsync() → GetAllProfiles().Count == 0; valid profile a is gone too.
CreateProfile("c") + SaveAsync() → profiles.json now contains only c. Profile a and its binding are permanently lost.
This differs from #157 (torn/syntactically corrupt file) and #148 (concurrent saves): here the file is valid JSON with one off-schema element, the case #125/#133 meant to make survivable. The #157 fix (atomic writes, moving a corrupt file aside) would prevent the permanent loss but still load zero profiles.
Suggested fix
Deserialize to List<JsonElement> (or JsonArray), then per element call element.Deserialize<ProfileDto>(_jsonOptions) / <CommandDto> inside a try that catches both JsonException and ArgumentException, skipping only that entry. Apply the same per-ChordDto within a profile.
Keep the whole-file catch only for a file that doesn't parse at all.
Regression tests: one entry with "name": 5 in profiles.json still loads the other profiles; same for a wrong-typed field in commands.json.
Recommended next step: Deserialize each element separately (from a List<JsonElement>, catching JsonException and ArgumentException per entry and per ChordDto), and add the two regression tests from the issue. This is a small, contained fix that is worth landing before #157.
Activity
matt-edmondson commentedon Oct 6, 2026
Triage
profiles.jsonorcommands.jsonsilently loads zero entries, and the next save permanently deletes every valid profile and binding. It has been reproduced. This is user data loss in exactly the case One invalid chord entry in profiles.json makes KeybindingManager.InitializeAsync throw, although a syntactically corrupt file is tolerated #125/One invalid or null entry in commands.json makes KeybindingManager.InitializeAsync throw, although a syntactically corrupt commands.json is tolerated #133 were meant to make survivable.Keybinding/Services/JsonKeybindingRepository.cs(load and save paths)Recommended next step: Deserialize each element separately (from a
List<JsonElement>, catchingJsonExceptionandArgumentExceptionper entry and perChordDto), and add the two regression tests from the issue. This is a small, contained fix that is worth landing before #157.Generated by Claude Code