Skip to content

fix(saves): re-register the device when RomM no longer has its id#1573

Open
danielcopper wants to merge 1 commit into
mainfrom
fix/1560-device-reregister
Open

fix(saves): re-register the device when RomM no longer has its id#1573
danielcopper wants to merge 1 commit into
mainfrom
fix/1560-device-reregister

Conversation

@danielcopper

Copy link
Copy Markdown
Owner

What this is

PR 2 of #1570. When RomM's database is wiped or restored, the device id the plugin cached no longer
exists on the server. Every device-scoped save call then gets a definitive 404, and the plugin was stuck
reporting the server as offline forever. PR 1 made that message honest (not_found); this PR makes it
recover: ensure_device_registered now detects that its cached device id is dead and re-registers a
fresh one instead of trusting the stale id blindly.

Closes #1560.

The verified facts this rests on

  • A device-scoped save call with a dead device id returns HTTP 404 ("Device with ID <uuid> not found"), surfaced as RommNotFoundError. A dead ROM id does not 404 the saves endpoint — it returns
    200 []. So a device-scoped 404 is unambiguously a dead device, never a dead ROM.
  • ensure_device_registered already calls update_device(cached_id) as a best-effort touch on every run
    and swallowed any exception there. That swallowed call is exactly where a dead cached id produces
    its 404 — the natural healing point, at zero extra requests.
  • The method runs on every plugin load (src/index.tsx, when save-sync is enabled) and before every
    save-sync. So installing the release — which reloads the plugin — applies the fix on its own. No manual
    step for the user.

The change

ensure_device_registered (py_modules/services/saves/sync_engine/devices.py): the best-effort
update_device touch now peels a definitive RommNotFoundError into forget_device() + fall-through to
the existing registration path, minting and persisting a fresh id and returning it (not the stale one).

The discipline that matters: only a RommNotFoundError heals. Every other exception — transport,
timeout, 403, generic — stays a best-effort swallow that keeps the cached id, so a server blip can never
throw away a valid registration and churn a re-register. The except RommNotFoundError is ordered before
the broad clause, and the broad clause binds no verdict slug, so the check_404_not_unreachable gate
(from PR 1) stays green. A re-register that itself fails after a forget returns a clean
{success: false, reason, message} with the id left cleared — a retriable state, no half-state.

Scope is one service method, its tests, and one docs note. The read callables (already honest via PR 1),
forget_device/register_device, the origin-change trigger, and the frontend are untouched.

Docs

docs/user-guide/troubleshooting.md — a device the server no longer has is re-registered automatically on
the next plugin load; the deleted/re-added-ROM re-check (a new rom_id — separate work) stays described as
the user's action.

Verification

mise run gate green: 6300 backend + 2238 frontend tests, ruff/basedpyright clean,
check_404_not_unreachable OK, 31/31 in test_devices.py. Every test asserts persisted kv_config state
(id changed on heal / kept on a transport failure / cleared on a failed re-register), not call counts.

On-device (backend path, can't be unit-tested): stage a device id the server no longer has (register,
then remove it server-side), load the plugin or launch a game, and confirm a fresh device appears in
RomM's device list with no offline claim; a genuine transient outage during the touch must NOT
re-register.

Closes part of #1570.

When RomM's database is wiped or restored, the device id the plugin cached
no longer exists server-side, so every device-scoped save call 404s. The
best-effort update_device touch in ensure_device_registered already makes
that 404 the natural, zero-extra-request healing point, but it swallowed
the error and trusted the dead id forever.

Peel a definitive RommNotFoundError off that touch: on a 404, forget the
dead id (clearing the kv_config row) and fall through to the existing
registration path so a fresh id is minted and persisted. Every other touch
failure stays swallowed best-effort — a transport blip must not throw away
a valid id and churn a re-registration. If the re-registration itself fails
mid-heal, the method returns the classified failure with an empty id and
the dead id left cleared, a clean state the next call retries.

Healing fires on every plugin load, so the fix applies itself the moment
the release is installed — no user action required.

Closes #1560.
@github-actions github-actions Bot added the area:saves Save file sync label Jul 23, 2026
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:saves Save file sync

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Cannot sync with server after romm_db restoration

1 participant