Seen by the worker on #638, as an intermittent failure of the Chrome browser suite on unchanged code.
Where
saveStateOnce() in src/shared/state.js reads the stored record, merges this page's changes into it, and writes it back. Nothing stops another page or the background from writing between that read and that write. The background's network switch after the user approves a site's wallet_switchEthereumChain (updateState() in src/background/) is one such write.
What happens
The popup starts a save (on navigation, or on its ten-second balance refresh) and reads the record. The background then saves the network the user just approved. The popup writes its merged copy, built from the record as it read it, so the network goes back to the one before the switch. In the Chrome suite, after a site's switch back to mainnet resolved, the stored network stayed on Sepolia, and the next test started on Sepolia; a second run on the same commit passed.
What should happen
A save never undoes a change another page or the background made after the save read the record: the network the user approved is the one stored.
Severity: major
Definition of done
No write by another page or the background between a save's read and its write is lost.
A unit test lands the background's write between the popup's read and write and checks the stored network, failing on next.
Model: opus-5-5
Seen by the worker on https://git.eeqj.de/sneak/AutistMask/pulls/638, as an intermittent failure of the Chrome browser suite on unchanged code.
## Where
`saveStateOnce()` in `src/shared/state.js` reads the stored record, merges this page's changes into it, and writes it back. Nothing stops another page or the background from writing between that read and that write. The background's network switch after the user approves a site's `wallet_switchEthereumChain` (`updateState()` in `src/background/`) is one such write.
## What happens
The popup starts a save (on navigation, or on its ten-second balance refresh) and reads the record. The background then saves the network the user just approved. The popup writes its merged copy, built from the record as it read it, so the network goes back to the one before the switch. In the Chrome suite, after a site's switch back to mainnet resolved, the stored network stayed on Sepolia, and the next test started on Sepolia; a second run on the same commit passed.
## What should happen
A save never undoes a change another page or the background made after the save read the record: the network the user approved is the one stored.
Severity: major
## Definition of done
- [ ] No write by another page or the background between a save's read and its write is lost.
- [ ] A unit test lands the background's write between the popup's read and write and checks the stored network, failing on `next`.
Model: opus-5-5
clawbot
self-assigned this 2026-10-20 13:18:49 +02:00
Plan: one PR to next. Two writers read, merge and write the stored record, each queued only within its own context: saveStateOnce() in src/shared/state.js (every popup or approval page) and updateStateOnce() in src/background/state.js. Both hold one lock across their read and their write, the browser's own cross-context lock (navigator.locks, shared by every page and the background of the extension), through one helper beside storageGet()/storageSet() in src/shared/browserApi.js, so a write by one can never land between the read and the write of the other. No silent fallback when the lock is missing: the extension runs only where it exists. A Jest test lands the background's network write between the popup's read and write and checks the stored network, failing on next.
Model: opus-5-5
Plan: one PR to `next`. Two writers read, merge and write the stored record, each queued only within its own context: `saveStateOnce()` in `src/shared/state.js` (every popup or approval page) and `updateStateOnce()` in `src/background/state.js`. Both hold one lock across their read and their write, the browser's own cross-context lock (`navigator.locks`, shared by every page and the background of the extension), through one helper beside `storageGet()`/`storageSet()` in `src/shared/browserApi.js`, so a write by one can never land between the read and the write of the other. No silent fallback when the lock is missing: the extension runs only where it exists. A Jest test lands the background's network write between the popup's read and write and checks the stored network, failing on `next`.
Model: opus-5-5
Done as planned in #644. Beyond the plan: the per-context queues are removed, since the lock also orders each context's own writes, and the StateRecovery reset takes the lock too.
Model: opus-5-5
Done as planned in https://git.eeqj.de/sneak/AutistMask/pulls/644. Beyond the plan: the per-context queues are removed, since the lock also orders each context's own writes, and the StateRecovery reset takes the lock too.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Seen by the worker on #638, as an intermittent failure of the Chrome browser suite on unchanged code.
Where
saveStateOnce()insrc/shared/state.jsreads the stored record, merges this page's changes into it, and writes it back. Nothing stops another page or the background from writing between that read and that write. The background's network switch after the user approves a site'swallet_switchEthereumChain(updateState()insrc/background/) is one such write.What happens
The popup starts a save (on navigation, or on its ten-second balance refresh) and reads the record. The background then saves the network the user just approved. The popup writes its merged copy, built from the record as it read it, so the network goes back to the one before the switch. In the Chrome suite, after a site's switch back to mainnet resolved, the stored network stayed on Sepolia, and the next test started on Sepolia; a second run on the same commit passed.
What should happen
A save never undoes a change another page or the background made after the save read the record: the network the user approved is the one stored.
Severity: major
Definition of done
next.Model: opus-5-5
Plan: one PR to
next. Two writers read, merge and write the stored record, each queued only within its own context:saveStateOnce()insrc/shared/state.js(every popup or approval page) andupdateStateOnce()insrc/background/state.js. Both hold one lock across their read and their write, the browser's own cross-context lock (navigator.locks, shared by every page and the background of the extension), through one helper besidestorageGet()/storageSet()insrc/shared/browserApi.js, so a write by one can never land between the read and the write of the other. No silent fallback when the lock is missing: the extension runs only where it exists. A Jest test lands the background's network write between the popup's read and write and checks the stored network, failing onnext.Model: opus-5-5
Done as planned in #644. Beyond the plan: the per-context queues are removed, since the lock also orders each context's own writes, and the StateRecovery reset takes the lock too.
Model: opus-5-5