Skip to content

fix(sync): WebDAV/Upstash sync loses customModels and other mergeWithUpdate fields - #6892

Closed
Qznan wants to merge 1 commit into
ChatGPTNextWeb:mainfrom
Qznan:codex/fix-webdav-sync-custom-models
Closed

Qznan wants to merge 1 commit into
ChatGPTNextWeb:mainfrom
Qznan:codex/fix-webdav-sync-custom-models

Conversation

@Qznan

@Qznan Qznan commented Sep 10, 2026

Copy link
Copy Markdown

Summary

Fixes a WebDAV/Upstash sync bug where locally edited customModels (and any other field merged via mergeWithUpdate) never reached the cloud, so other devices never received the change.

Root cause

app/utils/sync.tsmergeWithUpdate had two compounding bugs:

  1. remoteUpdateTime was mistakenly read from localState.lastUpdateTime instead of remoteState.lastUpdateTime, so the timestamp comparison was effectively localUpdateTime < localUpdateTime and always fell into the else branch.
  2. Both branches also swapped the merge direction: the if (remote newer) branch called merge(remoteState, localState) and returned { ...remoteState } (so localState was never updated), while the else branch called merge(localState, remoteState) (remote overwrote local).

Combined, the effective behavior was: remote always overwrote local, after which sync() uploaded localState (which now held the stale remote value). Locally edited fields such as customModels were therefore lost on every sync round-trip.

Fix

  • Read remoteUpdateTime from remoteState.lastUpdateTime.
  • Merge the newer side in-place into localState (which is what setLocalAppState and client.set subsequently upload):
    • remote newer → merge(localState, remoteState) so local adopts the remote value and uploads it.
    • local newer or equal → keep local untouched, so the new local value is preserved and uploaded.

Affected fields

Not limited to customModels. mergeWithUpdate is used by both the Config and Access stores, so the same bug affected modelConfig, ttsConfig, realtimeConfig, theme/fontSize/fontFamily, and every provider URL/Key/Secret stored in access.

Validation

  • Logic verified against 4 scenarios with a plain-JS port:
    1. local newer than remote → local value preserved ✓
    2. remote newer than local → local adopts remote value ✓
    3. local changed again after a previous sync → local new value preserved ✓
    4. equal timestamps → local wins ✓
  • yarn test:ci: 34 suites / 157 tests passed, including the existing test/merge.test.ts.

Files changed

  • app/utils/sync.ts (mergeWithUpdate only; +10/-6)

Notes

No behavioral change for users whose remote was always older than local (they already kept local). The fix specifically restores the intended "newer wins" semantics for the round-trip upload.

…/Upstash sync

mergeWithUpdate had two bugs that prevented local customModels (and any
other Config/Access field synced via mergeWithUpdate) from reaching the
cloud:

1. remoteUpdateTime was mistakenly read from localState.lastUpdateTime,
   making the timestamp comparison ineffective.
2. Both branches swapped the merge direction, so when remote was treated
   as older it still overwrote local with remote, then the empty local
   value was uploaded to the cloud.

Now the comparison uses remoteState.lastUpdateTime and the newer side is
merged in-place into localState (which is what setLocalAppState and
client.set later upload). Local wins on ties to avoid clobbering new
local edits with stale remote values.

Verified with 4 scenarios (local newer, remote newer, local changed
again, equal timestamps) and yarn test:ci (34 suites / 157 tests).
@Qznan Qznan closed this Sep 10, 2026
@Qznan
Qznan deleted the codex/fix-webdav-sync-custom-models branch September 10, 2026 04:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants