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)
- 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.
- 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.
- 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.
- 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]
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
common/change, stdlib bump, or contract change moves the WASM).Possible directions (each needs design scrutiny)
(4) is cheap and strictly better than today even if (1)-(3) take longer.
Related
The same publish surfaced an adjacent hazard: nova's
riverctlautoupdate had been building frommain, 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]