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
A power loss or OS crash just after a save can leave a zero-length settings file, which LoadOrCreate then silently overwrites with defaults #325
AppData.WriteText (AppDataStorage/AppData.cs:142-167) aims to be a safe write. It writes .tmp, copies the main file to .bk, deletes the main file, moves .tmp into place, then deletes .bk. That protects against the process crashing, but not against a power loss or OS crash:
Nothing is flushed to disk.FileSystem.File.WriteAllText(tempFilePath, text) only hands the data to the OS page cache. The Delete/Move/Delete calls that follow change metadata only. NTFS and ext4 journal metadata but not file data, so the rename and deletes can reach disk before the .tmp contents do. A crash in that window leaves <name>.json zero-length (or NUL-filled), and .bk is already gone.
The destination is deleted before the move, which bypasses ext4's auto_da_alloc safety net. ext4 flushes data when a file is renamed over an existing file. Here the move lands on a path that no longer exists, so that safety net never triggers.
An empty file is treated as "no settings".ReadText (AppData.cs:176-203) only runs the .tmp/.bk recovery path on FileNotFoundException. For a file that exists but is empty, it returns "". AppData<T>.LoadOrCreate then calls newAppData.Save() when string.IsNullOrEmpty(jsonString), which overwrites the file with defaults. Nothing is logged and nothing is thrown.
Failure scenario
The app saves: Save(), or QueueSave() followed by the debounced or process-exit flush. WriteText completes and deletes .bk.
Within the OS writeback interval (typically a few seconds), the machine loses power, blue-screens, or the laptop battery dies. The exit-time flush right before shutdown is a common point to be in this window.
After reboot, <type>.json exists with length 0 or NUL bytes, and there is no .bk or .tmp.
Write the temp file through a stream and call Flush(flushToDisk: true) before any metadata change.
Replace atomically instead of delete-then-move: use File.Replace(temp, filePath, bk) when the destination exists, or File.Move(temp, filePath, overwrite: true). The destination is then never missing, and ext4's rename-over heuristic applies.
Rationale: An OS crash or power loss shortly after a save wipes every user setting, and nothing is logged. The exit-time flush just before shutdown sits squarely in the risky window. The fix is contained: flush the temp file to disk, replace the destination atomically with File.Replace / Move(overwrite: true), and treat an empty file as unreadable. It is also straightforward to test with MockFileSystem.
Activity
matt-edmondson commentedon Sep 29, 2026
Triage
AppData.WriteText/ReadTextandAppData<T>.LoadOrCreateJsonException). Both end in silent loss of user settings, and the "archive rather than overwrite" behaviour this issue asks for is the same policy A settings file that fails to deserialize is deleted outright and replaced with defaults, with no backup left to recover from #314 needs, so land them together or A settings file that fails to deserialize is deleted outright and replaced with defaults, with no backup left to recover from #314 firstRationale: An OS crash or power loss shortly after a save wipes every user setting, and nothing is logged. The exit-time flush just before shutdown sits squarely in the risky window. The fix is contained: flush the temp file to disk, replace the destination atomically with
File.Replace/Move(overwrite: true), and treat an empty file as unreadable. It is also straightforward to test withMockFileSystem.Generated by Claude Code