diff --git a/src/renderer/src/components/settings/AccountsPane.test.tsx b/src/renderer/src/components/settings/AccountsPane.test.tsx
index a069bc44f..d9b5cd182 100644
--- a/src/renderer/src/components/settings/AccountsPane.test.tsx
+++ b/src/renderer/src/components/settings/AccountsPane.test.tsx
@@ -23,7 +23,7 @@ function renderPane(
describe('AccountsPane', () => {
beforeEach(async () => {
await i18n.changeLanguage('en')
- useAppStore.setState({ settingsSearchQuery: '' })
+ useAppStore.setState({ settingsSearchQuery: '', runtimeEnvironments: [] })
})
it('hides the WSL account location controls on platforms without WSL support', () => {
@@ -116,6 +116,14 @@ describe('AccountsPane', () => {
expect(markup).toContain(
'Showing accounts managed by the remote server. Add or re-authenticate accounts on that server.'
)
+ // Both the Claude and Codex sections must say local accounts are intact and
+ // link the default-runtime control, so the scoped list never reads as loss.
+ expect(markup.split('Accounts managed on this desktop are unchanged').length - 1).toBe(2)
+ expect(markup.split('Open Remote Servers').length - 1).toBe(2)
+ // Before the saved-server list loads there is no name to interpolate, so the
+ // scope label must stay bare instead of stuttering the prose fallback.
+ expect(markup).toContain('Account scope: Remote server<')
+ expect(markup).not.toContain('Remote server: the remote server')
// The WSL account-location toggle is a local concern; a remote owner hides it.
expect(markup).not.toContain('aria-label="Account location"')
const addAccountIndex = markup.indexOf('Add Account')
@@ -125,10 +133,34 @@ describe('AccountsPane', () => {
)
})
+ it('omits the scope control on the web client, which cannot select Local desktop', () => {
+ const webGlobal = globalThis as { window?: { __ORCA_WEB_CLIENT__?: boolean } }
+ const hadWindow = 'window' in webGlobal
+ webGlobal.window = { ...webGlobal.window, __ORCA_WEB_CLIENT__: true }
+ try {
+ const markup = renderPane({
+ ...getDefaultSettings('/tmp'),
+ activeRuntimeEnvironmentId: 'env-1'
+ })
+
+ // The web client has no desktop-managed accounts to switch back to, so
+ // this copy would promise a move it cannot make.
+ expect(markup).not.toContain('Accounts managed on this desktop are unchanged')
+ expect(markup).not.toContain('Open Remote Servers')
+ // The server-scope copy itself still applies.
+ expect(markup).toContain('Showing accounts managed by')
+ } finally {
+ if (!hadWindow) {
+ delete webGlobal.window
+ }
+ }
+ })
+
it('keeps local copy and enabled sign-in actions when no remote server is active', () => {
const markup = renderPane(getDefaultSettings('/tmp'))
expect(markup).toContain('Showing accounts for this device. New accounts are added there.')
+ expect(markup).not.toContain('Open Remote Servers')
const addAccountIndex = markup.indexOf('Add Account')
expect(addAccountIndex).toBeGreaterThan(0)
expect(
diff --git a/src/renderer/src/components/settings/AccountsPane.tsx b/src/renderer/src/components/settings/AccountsPane.tsx
index e9a1c0efc..6f96cd2e1 100644
--- a/src/renderer/src/components/settings/AccountsPane.tsx
+++ b/src/renderer/src/components/settings/AccountsPane.tsx
@@ -52,6 +52,8 @@ import {
getAccountsPaneSearchEntries
} from './accounts-search'
import { GrokAccountsSection } from './GrokAccountsSection'
+import { getRemoteAccountsPaneScope } from './provider-account-scope'
+import { ProviderHostScopeControl } from './ProviderHostScopeControl'
import { SearchableSetting } from './SearchableSetting'
import { SettingsRow, SettingsSegmentedControl } from './SettingsFormControls'
import { matchesSettingsSearch } from './settings-search'
@@ -77,6 +79,7 @@ import {
} from './provider-account-visibility'
import { translate } from '@/i18n/i18n'
import { cn } from '@/lib/utils'
+import { isWebClientLocation } from '@/lib/web-client-location'
import {
emptyClaudeAccountsState,
emptyCodexAccountsState,
@@ -350,9 +353,14 @@ export function AccountsPane({
// (see #7973); every list/select/remove below must scope to it, not host/WSL.
const isRemoteAccountScope = hasRemoteProviderAccountOwner(settings)
const activeRuntimeEnvironmentId = settings.activeRuntimeEnvironmentId?.trim() || null
- const remoteServerLabel = isRemoteAccountScope
+ // Why: keep the real name separate from the prose fallback below; the scope
+ // label must not interpolate the fallback.
+ const remoteServerName = isRemoteAccountScope
? (runtimeEnvironments.find((environment) => environment.id === activeRuntimeEnvironmentId)
- ?.name ??
+ ?.name ?? null)
+ : null
+ const remoteServerLabel = isRemoteAccountScope
+ ? (remoteServerName ??
translate('auto.components.settings.AccountsPane.remoteServerFallback', 'the remote server'))
: null
const accountRuntime: LocalAccountRuntime = isRemoteAccountScope
@@ -369,6 +377,21 @@ export function AccountsPane({
localAccountRuntime.runtime === 'host' && !navigator.userAgent.includes('Windows')
? `${localAccountRuntime.label.charAt(0).toLocaleLowerCase()}${localAccountRuntime.label.slice(1)}`
: localAccountRuntime.label
+ // Why: users read the remote-scoped list as their desktop accounts being
+ // deleted (#8186); say they are intact and link the default-runtime control.
+ // The web client has no desktop-owned accounts and cannot select Local
+ // desktop, so promising a switch back would be a dead end there.
+ const remoteAccountScopeNotice =
+ isRemoteAccountScope && !isWebClientLocation() ? (
+
+ ) : null
const [codexAccounts, setCodexAccounts] =
useState(emptyCodexAccountsState)
@@ -886,6 +909,7 @@ export function AccountsPane({
) : null}
+ {remoteAccountScopeNotice}