fix(accounts): accounts.json is safe across gclo processes; an unreadable file is not a corrupt one #156

Merged
james.maes merged 2 commits from fix/155-accounts-store into main 2026-11-10 22:06:08 +00:00
2 Commits
Author SHA1 Message Date
james.maesandClaude Fable 5.2 17955a3c7b fix(accounts): address the independent review of the shared accounts file
One rename puts the new file in place, so it is never absent; start-up
reads without the lock. An edit of an account another process removed is
refused; a newer recorded sync result is kept; a change that finds the
file gone or damaged is applied to what the store last read. Busy means a
sharing violation; a read-only file fails at once; a lock held for the
whole wait is an AccountsFileBusyException (CLI exit 4). Settings saves
retry briefly, use a temporary name of their own and leave nothing behind.

Refs #155

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-10 16:06:03 -06:00
james.maesandClaude Fable 5.2 c7880167ab fix(accounts): accounts.json is safe across gclo processes; an unreadable file is not a corrupt one
The store read accounts.json once and wrote that snapshot back on every
change. With the desktop app open, an account added from the CLI was
erased by the app's next write (recording a sync result was enough), and
its token stayed in the vault with nothing pointing at it. A long
`gclo sync --account` did the same to the app, and two app instances to
each other.

- Every change now takes a lock that every gclo process takes
  (accounts.json.lock, opened for one process alone), reads the file
  again, applies its one change by account id to what the file holds at
  that moment, and writes that. A sync result recorded for an account
  another process deleted is ignored, not written back.
- A file that cannot be read is no longer taken for a corrupt one. Only
  content that is not a valid list of accounts is moved aside. A file in
  use is tried again for a short while; one that stays unreadable is left
  where it is: the store starts empty and logs why, and a change fails
  without writing anything.
- The swap of the new file into place is tried again when something
  outside gclo holds the live file for a moment.

settings.json is written beside the file and moved into place, so a crash
mid-write leaves the previous settings. A failed load or save is logged,
and Save says whether it succeeded. The "0 means absent" special case for
the splash duration described something that does not happen (an absent
field keeps the default); the value is simply brought into range.

Tests: the three classes that set GCLO_DATA_DIR for the process now share
one collection, so they never run beside each other.

docs/CLI.md named the wrong folder for accounts.json; corrected.

Closes #155

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-10 15:57:30 -06:00