Skip to content

fix(remote): never overwrite remote-hosts.json while it cannot be read - #1575

Merged
vastsa merged 3 commits into
vastsa:mainfrom
SeanLiu326:fix/remote-hosts-registry-writable-gate
Oct 12, 2026
Merged

vastsa merged 3 commits into
vastsa:mainfrom
SeanLiu326:fix/remote-hosts-registry-writable-gate

Conversation

@SeanLiu326

Copy link
Copy Markdown
Contributor

Fixes #1572

Summary

  • readFileContents becomes a discriminated read state: ok / absent / unreadable (IO error) / corrupt (JSON parse failure) / unsupportedVersion (shape or version mismatch).
  • Reads (list) stay permissive exactly as before — the UI shows an empty list while the file is unavailable, and nothing on disk changes.
  • Writes (upsert, remove) may only proceed from a verifiably readable file: an unreadable file makes the write throw REMOTE_REGISTRY_UNWRITABLE, a future-version file throws REMOTE_REGISTRY_VERSION_UNSUPPORTED, and a corrupt file is quarantined to remote-hosts.json.corrupt-<timestamp> (bytes preserved) before pairing continues.

Rationale

#1572: a transient read error (AV/backup holding the file on Windows) or a version-skewed file degraded to an empty list, and the next pairing atomically wrote that list back — silently wiping every paired host and its encrypted device tokens. The in-code comment acknowledged "any upsert would overwrite it"; this change makes the overwrite impossible instead of acknowledged.

Quarantine (rather than refuse) for the corrupt case keeps pairing available on a genuinely broken file while the old records stay recoverable; refusing on unreadable/version-skew is strict because those files are intact and worth protecting as-is.

Affected specs / ADRs / E2E scenarios

  • No spec or ADR covers the remote-host registry file lifecycle (the remote MVP specs cover the connection/pairing flows), so none updated.
  • pairing-boot-add-remove E2E-style suite: re-run green (5/5); no new scenario added since the failure mode is a filesystem-level fault injection covered by the new unit tests.

Validation (on latest origin/main eec988c)

  • apps/desktop/test/remote-host-registry.test.mjs: 11/11 — 8 existing (one, "shape-mismatched … preserved on disk until rewritten", still passes unchanged since list behavior is untouched) plus 3 new: unreadable file (directory at the registry path) rejects upsert/remove and survives untouched; future-version file is listed empty but never overwritten; corrupt file is quarantined with its bytes intact and pairing proceeds.
  • tsc --noEmit in apps/desktop: pass. settings-remote-hosts (3), boot-remote-hosts (8), pairing-boot-add-remove (5): green.
  • remote-host-ssh-password: 5 failures — pre-existing Windows-environment failures in ssh-askpass/ssh-transport (POSIX helper + fixture spawn), a file this change never touches (verified: it imports only ssh-askpass.ts).

Compatibility / remaining risk

  • Pairing into a locked file now fails with a clear typed error instead of "succeeding" destructively; the renderer pairing flow already surfaces IPC errors, so users retry when the lock clears.
  • remote-hosts.json.corrupt-<timestamp> files accumulate only when corruption actually happens (rare), each preserving the pre-corruption bytes.
  • No schema or API changes.

A transient read error (antivirus/backup holding the file) or a file
written by a newer app version previously degraded to an empty list, and
the next pairing atomically wrote that list back — silently wiping every
paired host, device tokens included (vastsa#1572).

Split the read into a discriminated state. Reads stay permissive (the UI
shows an empty list as before, nothing on disk changes); upsert and
remove now write only from a verifiably readable file: an unreadable or
future-version file makes the write throw REMOTE_REGISTRY_UNWRITABLE /
REMOTE_REGISTRY_VERSION_UNSUPPORTED, and a corrupt file is quarantined
under remote-hosts.json.corrupt-<timestamp> with its bytes preserved
before pairing continues.
Concurrent pairings can read the same corrupt registry, race quarantine, and overwrite records. Serialize each read-modify-write so every mutation observes the preceding completed state.
@vastsa
vastsa merged commit d5fa57d into vastsa:main Oct 12, 2026
5 checks passed
@vastsa

vastsa commented Oct 12, 2026

Copy link
Copy Markdown
Owner

Thanks for the fix. The data-loss scenario was real, and your read/write guard addressed the root cause. I added registry-wide mutation serialization and a concurrent-pairing regression test so separate host pairings cannot race through quarantine and writeback. Merged as d5fa57d.

@SeanLiu326
SeanLiu326 deleted the fix/remote-hosts-registry-writable-gate branch October 15, 2026 09:06

This branch was previously deployed

1 inactive deployment
Preview — c041efd9 Deployed Oct 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] remote-hosts.json: a transient read error or unknown file version silently wipes all paired hosts on the next pairing

2 participants