Keep rapid settings edits from reverting each other#4494
Conversation
Server settings only change locally once the server echoes `settingsUpdated` back over the websocket, and `useUpdateSettingsTarget` applied nothing locally in the meantime. Any edit issued inside that round trip was therefore computed from pre-edit settings, and because keys such as `providerInstances` are whole-value replacements rather than deep merges, the second write silently reverted the first: toggle one provider off, toggle a second off a moment later, and the first provider's switch snaps back on and never reaches settings.json. Retain in-flight patches per environment and replay them over the server value with the same `applyServerSettingsPatch` the server runs, so reads and follow-up patches build on the edit the user just made. Patches are dropped once no write is outstanding — including after a failed write, so the server value stays authoritative. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4df7b36. Configure here.
| environmentId, | ||
| input: { patch: serverPatch }, | ||
| }).finally(() => { | ||
| releasePendingServerPatch(environmentId); |
There was a problem hiding this comment.
Overlay drops before settings echo
High Severity
Optimistic pending server patches are cleared when the RPC promise settles, but the authoritative primaryServerSettingsAtom updates later via a websocket event. This gap briefly exposes stale server settings, causing follow-up edits to potentially revert earlier in-flight changes.
Reviewed by Cursor Bugbot for commit 4df7b36. Configure here.
ApprovabilityVerdict: Needs human review An unresolved high-severity comment identifies a potential timing bug where patches are released when the RPC settles but before the websocket echo arrives, which could still cause the race condition this PR aims to fix. This substantive correctness concern warrants human review. You can customize Macroscope's approvability policy. Learn more. |


What Changed
useUpdateSettingsTargetnow retains each in-flight server settings patch and replays it over the server value (via the sameapplyServerSettingsPatchthe server runs) until no write is outstanding for that environment.apps/web/src/hooks/pendingServerSettings.tsholds the per-environment overlay: retain on dispatch, release when the write settles, drop the overlay only when the last outstanding write settles.useMergedSettingsapplies the overlay before merging client settings, sousePrimarySettings/useEnvironmentSettingsreflect the edit immediately.pendingServerSettings.test.ts.Why
Server settings only change locally once the server echoes
settingsUpdatedback over the websocket. Until then, readers still see pre-edit settings, so a second edit issued inside that window is computed from stale state — and because keys such asproviderInstancesare whole-map replacements rather than deep merges (applyServerSettingsPatch), the second write silently reverts the first.Reproduction before this change, in Settings → Providers: toggle Codex off, then toggle Claude off a moment later. Codex's switch snaps back on and its
enabled: falsenever reachessettings.json— only the Claude entry is persisted. The same race applies to every provider-instance edit (binary path, custom models, accent color, add/delete), plusfavoritesandproviderModelPreferences, since those patches are also built from the settings snapshot.The doc comment on
useUpdateSettingsTargetalready claimed server keys were patched optimistically; this makes that true.UI Changes
No visual changes. Behavioral only: a provider toggle now flips immediately and stays flipped instead of reverting when another edit follows quickly.
Verified in a local build (server + web from this branch, driven through the browser): toggling two providers in immediate succession leaves both switches off and persists both entries to
settings.json; an off/on/other three-click sequence composes correctly. Onmainthe same first sequence loses the first toggle.Checklist
Validation:
vp test run src/hooks/pendingServerSettings.test.ts(6 passed),vp test run src/hooks src/components/settings src/providerInstances.test.ts(86 passed),vp run typecheckinapps/web,vp lint apps/web/src/hooks/,vp fmt --check.Note
Medium Risk
Touches how all server-backed settings are read and written in the web app; behavior is localized to the settings hooks but affects any whole-map patch (providers, favorites, etc.).
Overview
Fixes a race where back-to-back server settings edits could undo each other because the UI only updated after the websocket
settingsUpdatedecho, and patches likeproviderInstancesreplace the whole map.Adds
pendingServerSettings: each dispatched server patch is retained per environment, replayed withapplyServerSettingsPatchfor reads, and cleared only when the last in-flight RPC settles (including on failure).useMergedSettingsapplies that overlay before merging client settings;useUpdateSettingsTargetcallsretainPendingServerPatchon dispatch andreleasePendingServerPatchinfinallyinstead of relying on atom optimistic updates alone.Includes unit tests for overlay composition, refcounting, environment scoping, and subscriptions.
Reviewed by Cursor Bugbot for commit 4df7b36. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Prevent rapid server settings edits from reverting each other with an optimistic patch overlay
useMergedSettingsso the UI reflects the change before the server responds.usePendingServerPatchesbridges the patch state into React viauseSyncExternalStoreso components re-render when pending patches change.Macroscope summarized 4df7b36.