Skip to content

fix: keep windows.json and stylesheet.css intact when a save fails - #587

Open
prakash-aryan wants to merge 1 commit into
forge-ext:mainfrom
prakash-aryan:fix/keep-config-on-failed-write
Open

prakash-aryan wants to merge 1 commit into
forge-ext:mainfrom
prakash-aryan:fix/keep-config-on-failed-write

Conversation

@prakash-aryan

Copy link
Copy Markdown

Refs #415. This complements #536 and #579, which make Forge start with the default rules when windows.json is already empty or broken. This change stops Forge from emptying the file in the first place.

Problem

set windowProps and ThemeManagerBase._updateCss() save with Gio.File.replace_contents(). When a write inside it fails part way, for example with ENOSPC on a full disk, GLib still closes the stream normally and renames the partially written temporary file over the original. On a full disk that file is empty. This is GLib behaviour, and it is still present on GLib main.

windowDestroy() calls removeFloatOverride(), which saves windows.json every time a window is closed. Closing any window while the disk is full therefore empties the file, and the next login fails with:

SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data @ .../lib/shared/settings.js:140:26

This matches the comment in #415 about the file being emptied when /home filled up. It happened to me twice, each time a few minutes after the disk filled.

Change

Both saves now use GLib.file_set_contents(), which deletes its temporary file when a write fails and leaves the original in place. Successful saves behave as before: like REPLACE_DESTINATION, it replaces a symlink with a regular file and does not keep the old permissions.

A failed save still throws, as it did before. This change only keeps the existing file intact.

Testing

GNOME Shell 46.0, GJS 1.80.2 and GLib 2.80.0 on Ubuntu 24.04. ~/.config/forge was a 64 KiB tmpfs filled to 0 bytes free.

End to end, in a headless GNOME Shell (--headless --virtual-monitor) with its own XDG directories, D-Bus session and keyfile settings backend, running Forge from this repository:

Step main This branch
First start, Forge state ACTIVE ACTIVE
Close a window on the full disk, windows.json size 2223 → 0 bytes 2223 → 2223 bytes
Free the space and start again, Forge state ERROR (JSON.parse) ACTIVE

Each save path called directly from GJS, each on a freshly filled filesystem:

Save main This branch
configMgr.windowProps = ... 2223 → 0 bytes 2223 → 2223 bytes
_updateCss() 2580 → 0 bytes 2580 → 2580 bytes

No temporary files were left behind in any run. With free space, both branches save and read back the same rules, produce the same file mode and replace a symlinked windows.json with a regular file. prettier@2.7.1 --check is clean.

To reproduce by hand, mount a small tmpfs on ~/.config/forge, copy the existing files back into it, fill it with dd if=/dev/zero of=~/.config/forge/fill, close a tiled window and restart GNOME Shell.

Gio.File.replace_contents() writes to a temporary file and renames it
over the destination when the stream is closed. When a write fails part
way, for example with ENOSPC on a full disk, GLib still closes the stream
normally, so the partially written temporary file replaces the original.
On a full disk that file is empty.

Forge saves windows.json every time a window is closed, so a full disk
empties it and the next login fails with "JSON.parse: unexpected end of
data" (forge-ext#415). The stylesheet is saved the same way.

GLib.file_set_contents() removes its temporary file when a write fails
and leaves the original in place. Like the REPLACE_DESTINATION flag used
before, it replaces symlinks and does not keep the old permissions.
@juarezr juarezr added the bug Something isn't working label Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants