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
Every SaveAsync resets profiles.json, commands.json and active-profile.json to 0644 and replaces symlinked data files with regular ones #221
JsonKeybindingRepository.WriteFileUnlockedAsync (Keybinding/Services/JsonKeybindingRepository.cs, lines ~403–419 at 89f14b9) does this for each data file:
The temp file is created fresh, so after the move:
On Unix: each data file has the umask default mode (usually 0644), whatever mode it had before. A user who restricts the keybinding data to 0600 gets world-readable files back on the next save.
When a data file is a symlink: the rename replaces the link with a regular file, and the link's target stops being updated.
On Windows (not tested):MoveFileEx with replace leaves the temp file's inherited ACL in place of any explicit ACL the original had. File.Replace would keep it.
Reproduction (Linux, .NET 10)
KeybindingManager initialise → bind file.save to Ctrl+S → SaveAsync → chmod 0600 all three files → rebind → SaveAsync:
When path exists on non-Windows, create the temp file with UnixCreateMode = File.GetUnixFileMode(path) (via FileStreamOptions), or call File.SetUnixFileMode on it before the move.
Use File.Replace(tempPath, path, null) when the destination exists, which keeps the Windows ACL. Keep File.Move for the first write.
Resolve a symlinked path to its final target and write the temp file next to that target.
Acceptance criteria:
After a save over existing 0600 files, they are still 0600.
A symlinked data file is still a symlink after a save, and its target holds the new content.
Saves stay atomic: the target is never truncated in place.
Navigation's JsonFilePersistenceProvider has the same problem and is filed separately.
Priority: High. Every save quietly widens files the user restricted to 0600 back to world-readable, and turns a symlinked data file into a regular file, so the link's real target stops getting updates (silent data divergence for anyone keeping config in a dotfiles repo). It happens on the normal save path, with no unusual trigger.
Area / suggested assignment:JsonKeybindingRepository.WriteFileUnlockedAsync (Keybinding/Services/JsonKeybindingRepository.cs), the atomic temp-file save.
Next step: Implement the three suggested changes. Add Unix-only tests for mode preservation and for a symlink surviving a save. Keep the atomicity guarantee from the acceptance criteria.
Activity
matt-edmondson commentedon Nov 5, 2026
Triage
0600back to world-readable, and turns a symlinked data file into a regular file, so the link's real target stops getting updates (silent data divergence for anyone keeping config in a dotfiles repo). It happens on the normal save path, with no unusual trigger.JsonKeybindingRepository.WriteFileUnlockedAsync(Keybinding/Services/JsonKeybindingRepository.cs), the atomic temp-file save.File.Replacewhen the target exists). No open PR fixes it. Open PR Resolve a relative data directory once, for both the files and the lock [patch] #220 ("Resolve a relative data directory once, for both the files and the lock") touches path handling in the same repository, so land it first and resolve the symlink on top of the path it settles on.Generated by Claude Code