Skip to content

feat(editor): protect config saves — atomic write + staleness guard against external edits #116

Closed

Description

@falbany

Problem

The YAML config editor (issue #95, merged via PR #96) saves with a plain write_text and no staleness check. Two failure modes for the user's only .profiles file:

  1. Torn write — ProFiles crashing or losing power mid-save truncates the config file; the next launch falls back to defaults with no recovery copy.
  2. Silent clobber — the editor's own header button "Open in OS" invites editing the file in an external editor. Any change made there between the editor's load and Save is overwritten without warning: the modal reports SAVED and the external edits are gone.

Evidence

  • src/profiles/server/routes/config.py:419 — config_path.write_text(payload.content, encoding="utf-8"): no temp-file + os.replace, and SaveRequest carries no baseline (hash/mtime) to detect that the file changed since it was loaded.
  • GET /api/config/content (src/profiles/server/routes/config.py:364-398) returns {filename, content, config_path, session_trial} — no version marker of any kind, so the client cannot implement the guard without a server change.
  • The external-edit path is a designed flow of the editor (header "Open in OS" is the sole openConfigFile() consumer per the feat(web): YAML config editor in the UI #96 design); Revert re-fetches, but Save never re-checks the disk state.

Proposed approach

  • Core: add an atomic write helper in profiles.core.config.io.yaml_io (write to a temp file in the same directory, then os.replace); route both /api/config/save and /api/config/save-as through it.
  • Staleness guard: extend GET /content to also return the file's content hash; SaveRequest gains an optional baseline hash; /save returns 409 when the on-disk file no longer matches the baseline the editor loaded. Optional field → older callers unaffected.
  • Web: on 409, offer "Reload disk version / Overwrite anyway" via the existing ConfirmDialog; pass the baseline on every save from ConfigEditorModal.
  • Scope M. No breaking change (baseline is optional; 409 only fires for clients that send it).

Alternatives considered

  1. Live file watcher (client or server) — heavier machinery and still races at the save instant; a save-time check catches the actual data-loss moment. A watcher can be a follow-up if users hit concurrent editing often.
  2. Backup-on-save (.profiles.bak) — mitigates loss after the fact but does not prevent the silent clobber, and adds directory clutter; the 409 guard prevents the loss instead.

Proposed during automated enhancement scan 2026-11-04

Activity

  1. added this to the v1.0.0 milestone on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions