Prevent concurrent reflex.json update corruption

Merged
AL
16d ago
+920-26

Concurrent CLI commands can read the same JSON metadata and overwrite each other's updates or expose a partial document. Serialize read-modify-write operations with standard-library OS locks and atomically replace the target only after the complete JSON document has been written and synced. Frontend initialization uses the same locks while replacing .web.

Locks live under the stable per-user Reflex directory. Contention waits for acquisition, including repeated Windows lock timeouts. If that directory is read-only or the OS lock is unavailable, continue with an unlocked atomic write: readers still see complete JSON, but concurrent updates may overwrite one another. No runtime dependency is added. The shared helper resolves the target once, keeps its lock-directory constant private, and uses one exclusive attempt to create the staging file.

Validation:

  • Regression tests first reproduced permission-denied and read-only filesystem failures; fallback coverage now includes directory creation, lock-file opening, and OS lock acquisition.
  • 169 focused JSON/frontend test cases passed in the full suite, including process contention, directory replacement, crash recovery, and atomic reads with and without locking.
  • Full unit suite with Node 24: 11,283 passed and 159 skipped; 79.01% coverage.
  • The Windows contention regression failed before the fix and covers retrying timeouts while preserving permission-error fallback. A temporary nonblocking-lock mutation made all three process/cleanup concurrency cases fail; the restored blocking implementation passes.
  • Repository-wide Ruff lint/format and Pyright checks passed, as did all applicable commit hooks (including ty).
  • uv lock --check --offline and git diff --check passed.

Lock files remain in place so waiting writers retain the same lock identity. A hard process exit can leave a staging file, but never installs a partial document at the public path.