Skip to content

Users stranded on the old room contract after a re-key have no in-app signal to update #580

Description

@sanity

Problem

When a publish re-keys the room contract, the new UI derives the new contract key and migrates state forward. But a user whose browser is still running the old cached webapp keeps reading and writing the old contract key. They see a room that looks fine and simply goes quiet, while everyone who has refreshed is talking on the new key. There is no in-app indication that anything happened, and nothing prompts them to reload.

Observed on the 2026-07-31 publish (river-core 0.1.19, room contract V31 / #572). Mitigation was manual: post a message to the old contract using a riverctl build that still targets the old key, telling people to hard-refresh (Ctrl+Shift+R / Cmd+Shift+R). That works, but it is a one-off rescue that depends on someone remembering to do it, having an old-key binary on hand, and doing it before that binary is upgraded.

The failure is quiet in both directions, which is what makes it bad: the stranded user sees no error, and the people on the new contract cannot tell that anyone is missing.

Why this is worth a real fix

  • It recurs on every room-contract re-key, and re-keys are routine (any common/ change, stdlib bump, or contract change moves the WASM).
  • The stranded user's messages are not lost, but they are invisible to everyone else, so they may keep talking into a dead room for a long time.
  • The current rescue path is fragile and easy to forget under release pressure.

Possible directions (each needs design scrutiny)

  1. Version beacon + in-app prompt. The webapp knows the contract key it was built for. If it can cheaply learn that a newer generation exists, show a non-dismissable banner ("A new version of River is available — reload to continue"). Needs a trustworthy, cheap signal that does not require reading the new contract.
  2. Service-worker / cache-busting on publish. Make the shell notice a new webapp version and prompt (or auto-reload). Narrower than (1) — it fixes the stale-cache half without touching contract logic.
  3. Forward-migrate new messages onto the old contract (@sanity's suggestion). Keeps stranded users seeing traffic. Needs careful thought before adopting: it means writing to a contract the network is trying to retire, it doubles write traffic for the migration window, it raises the question of when writes stop, and a naive implementation could ping-pong state between generations or resurrect a room that was meant to be superseded. Worth exploring, but not obviously safe.
  4. Automate the rescue. At minimum, make "post an update notice to the outgoing contract" a scripted, documented step of the publish flow rather than an ad-hoc manual action.

(4) is cheap and strictly better than today even if (1)-(3) take longer.

Related

The same publish surfaced an adjacent hazard: nova's riverctl autoupdate had been building from main, so it ran ahead of the published UI across the re-key and posted the v0.2.116 release announcement to a contract nobody was reading. That has been fixed separately (the cron now tracks released versions from crates.io), but it is the same underlying shape — anything that derives the contract key must move in lockstep with the published UI.

[AI-assisted - Claude]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions