fix: scope localStorage settings key by basePath (#47481)#47932
fix: scope localStorage settings key by basePath (#47481)#47932steipete merged 1 commit intoopenclaw:mainfrom
Conversation
…loyment conflicts - Add settingsKeyForGateway() function similar to tokenSessionKeyForGateway() - Use scoped key format: openclaw.control.settings.v1:https://example.com/gateway-a - Add migration from legacy static key on load - Fixes openclaw#47481
Greptile SummaryThis PR correctly fixes a localStorage key collision that occurred when multiple Gateway instances were deployed under the same domain with different basePaths. The settings key is now scoped per gateway URL ( Findings:
Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: ui/src/ui/storage.ts
Line: 196-197
Comment:
**Legacy key never removed after migration**
When `loadSettings` (or `persistSettings`) reads from the legacy key `"openclaw.control.settings.v1"` and successfully migrates to the new scoped key, the old entry is never deleted from `localStorage`. While this is not a functional bug today (the scoped key takes priority), it leaves stale data in storage indefinitely and could cause confusion if the scoped key is somehow absent in the future.
Consider explicitly cleaning up the legacy key after a successful migration write:
```typescript
// After storage?.setItem(scopedKey, ...) in persistSettings:
storage?.removeItem("openclaw.control.settings.v1");
```
Similarly in `loadSettings`, once settings are loaded from the legacy key and `persistSettings` is called (or immediately after the initial load), the legacy key could be removed to avoid it persisting forever.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: ui/src/ui/storage.ts
Line: 195-197
Comment:
**Comment describes lookup order backwards**
The comment says _"First check for legacy key (no scope), then check for scoped key"_, but the actual code does the opposite — it checks the scoped key first and falls back to the legacy key last. The same backwards comment appears at line 268 in `persistSettings`. This will confuse future readers who expect the described order to match the actual precedence.
The comment should read something like: _"First check for scoped key, then fall back to legacy key for migration"_.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 389b83f |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 389b83fc64
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const scopedKey = settingsKeyForGateway(defaults.gatewayUrl); | ||
| const raw = storage?.getItem(scopedKey) ?? storage?.getItem(SETTINGS_KEY_PREFIX + "default") ?? storage?.getItem("openclaw.control.settings.v1"); |
There was a problem hiding this comment.
Load settings using current page scope key
Saving now writes to a key derived from next.gatewayUrl, but loading only reads the key derived from defaults.gatewayUrl (the page’s inferred gateway). If a user sets a custom gateway URL (different host/basePath) and refreshes, the saved entry is never read, so loadSettings() falls back to defaults and silently loses the user’s gateway/theme/session preferences. This is reproducible by calling saveSettings() with a non-default gatewayUrl and then loadSettings() on the same page.
Useful? React with 👍 / 👎.
| const scopedKey = settingsKeyForGateway(defaults.gatewayUrl); | ||
| const raw = storage?.getItem(scopedKey) ?? storage?.getItem(SETTINGS_KEY_PREFIX + "default") ?? storage?.getItem("openclaw.control.settings.v1"); |
There was a problem hiding this comment.
Legacy key never removed after migration
When loadSettings (or persistSettings) reads from the legacy key "openclaw.control.settings.v1" and successfully migrates to the new scoped key, the old entry is never deleted from localStorage. While this is not a functional bug today (the scoped key takes priority), it leaves stale data in storage indefinitely and could cause confusion if the scoped key is somehow absent in the future.
Consider explicitly cleaning up the legacy key after a successful migration write:
// After storage?.setItem(scopedKey, ...) in persistSettings:
storage?.removeItem("openclaw.control.settings.v1");Similarly in loadSettings, once settings are loaded from the legacy key and persistSettings is called (or immediately after the initial load), the legacy key could be removed to avoid it persisting forever.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/storage.ts
Line: 196-197
Comment:
**Legacy key never removed after migration**
When `loadSettings` (or `persistSettings`) reads from the legacy key `"openclaw.control.settings.v1"` and successfully migrates to the new scoped key, the old entry is never deleted from `localStorage`. While this is not a functional bug today (the scoped key takes priority), it leaves stale data in storage indefinitely and could cause confusion if the scoped key is somehow absent in the future.
Consider explicitly cleaning up the legacy key after a successful migration write:
```typescript
// After storage?.setItem(scopedKey, ...) in persistSettings:
storage?.removeItem("openclaw.control.settings.v1");
```
Similarly in `loadSettings`, once settings are loaded from the legacy key and `persistSettings` is called (or immediately after the initial load), the legacy key could be removed to avoid it persisting forever.
How can I resolve this? If you propose a fix, please make it concise.| // First check for legacy key (no scope), then check for scoped key | ||
| const scopedKey = settingsKeyForGateway(defaults.gatewayUrl); | ||
| const raw = storage?.getItem(scopedKey) ?? storage?.getItem(SETTINGS_KEY_PREFIX + "default") ?? storage?.getItem("openclaw.control.settings.v1"); |
There was a problem hiding this comment.
Comment describes lookup order backwards
The comment says "First check for legacy key (no scope), then check for scoped key", but the actual code does the opposite — it checks the scoped key first and falls back to the legacy key last. The same backwards comment appears at line 268 in persistSettings. This will confuse future readers who expect the described order to match the actual precedence.
The comment should read something like: "First check for scoped key, then fall back to legacy key for migration".
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/storage.ts
Line: 195-197
Comment:
**Comment describes lookup order backwards**
The comment says _"First check for legacy key (no scope), then check for scoped key"_, but the actual code does the opposite — it checks the scoped key first and falls back to the legacy key last. The same backwards comment appears at line 268 in `persistSettings`. This will confuse future readers who expect the described order to match the actual precedence.
The comment should read something like: _"First check for scoped key, then fall back to legacy key for migration"_.
How can I resolve this? If you propose a fix, please make it concise.|
Landed via temp rebase onto main.
Thanks @bobBot-claw! |
Fixes #47481 - Control UI localStorage settings conflict across different basePath deployments
Problem
When multiple Gateway instances are deployed under the same domain with different basePaths (e.g., /gateway-a/, /gateway-b/), the Control UI shares a single localStorage key "openclaw.control.settings.v1", causing settings from one Gateway to incorrectly apply to another.
Solution
settingsKeyForGateway()function to scope settings key by gateway URLopenclaw.control.settings.v1:https://example.com/gateway-aopenclaw.control.token.v1:...)Changes
ui/src/ui/storage.ts: Modified KEY constant and added settingsKeyForGateway()