fix(desktop): warn on proxy-only settings takeover #1219

This commit is contained in:
程序员阿江(Relakkes)
2026-08-16 00:33:26 +08:00
parent 5d7e1dcada
commit 7eb406243e
9 changed files with 74 additions and 0 deletions
@@ -246,6 +246,7 @@ describe('Settings > General tab', () => {
traceCapture: { enabled: true, storageDir: '/Users/test/.claude/cc-haha/traces' },
chatSendBehavior: 'enter',
responseLanguage: '',
proxyManagedSettingsWarning: false,
uiZoom: 1,
webSearch: { mode: 'auto', tavilyApiKey: '', braveApiKey: '' },
network: {
@@ -1710,6 +1711,17 @@ describe('Settings > General tab', () => {
expect(screen.getByText('MCP')).toBeInTheDocument()
expect(screen.getByText('Plugins')).toBeInTheDocument()
})
it('warns when the user settings contain only a proxy-managed placeholder', async () => {
useSettingsStore.setState({ proxyManagedSettingsWarning: true })
render(<Settings />)
fireEvent.click(screen.getByRole('button', { name: 'General' }))
expect(await screen.findByRole('alert')).toHaveTextContent(
'Your user settings contain only a PROXY_MANAGED proxy placeholder',
)
})
})
describe('Settings > Providers tab', () => {
+1
View File
@@ -1443,6 +1443,7 @@ Row 9, all 8 cells: continuing from straight down, turning left through lower-le
// Settings > General
'settings.general.appearanceTitle': 'Appearance',
'settings.general.proxyManagedSettingsWarning': 'Your user settings contain only a PROXY_MANAGED proxy placeholder, so another proxy tool may have replaced the remaining settings. If this was not intentional, disable CC Switch routing takeover and restore settings.json from a backup. Otherwise, you can ignore this notice.',
'settings.general.appearanceDescription': 'Switch between the six paper and ink palettes.',
'settings.general.appearance.paper': 'Paper',
'settings.general.appearance.warmClassic': 'Warm Classic',
+1
View File
@@ -1445,6 +1445,7 @@ export const jp: Record<TranslationKey, string> = {
// Settings > General
'settings.general.appearanceTitle': '外観',
'settings.general.proxyManagedSettingsWarning': 'ユーザー設定が PROXY_MANAGED プロキシのプレースホルダーだけになっており、他の設定がプロキシツールに上書きされた可能性があります。意図した設定でなければ、CC Switch のルーティング引き継ぎを無効にして、バックアップから settings.json を復元してください。意図した設定なら、この通知は無視できます。',
'settings.general.appearanceDescription': '紙と墨の 6 つの配色を切り替えます。',
'settings.general.appearance.paper': '紙墨',
'settings.general.appearance.warmClassic': 'クラシックウォーム',
+1
View File
@@ -1445,6 +1445,7 @@ export const kr: Record<TranslationKey, string> = {
// Settings > General
'settings.general.appearanceTitle': '모양',
'settings.general.proxyManagedSettingsWarning': '사용자 설정에 PROXY_MANAGED 프록시 자리표시자만 남아 있어 다른 설정이 프록시 도구에 의해 덮어쓰였을 수 있습니다. 의도한 설정이 아니라면 CC Switch 라우팅 인계를 끄고 백업에서 settings.json을 복원하세요. 직접 설정한 경우에는 이 알림을 무시해도 됩니다.',
'settings.general.appearanceDescription': '종이와 먹의 여섯 가지 배색을 전환합니다.',
'settings.general.appearance.paper': '지묵',
'settings.general.appearance.warmClassic': '따뜻한 클래식',
+1
View File
@@ -1444,6 +1444,7 @@ export const zh: Record<TranslationKey, string> = {
// Settings > General
'settings.general.appearanceTitle': '配色主題',
'settings.general.proxyManagedSettingsWarning': '偵測到使用者設定只剩 PROXY_MANAGED 代理占位資訊,其他設定可能被代理工具覆寫。若這不是你的主動設定,請先關閉 CC Switch 路由接管並從備份還原 settings.json;主動使用此設定可忽略。',
'settings.general.appearanceDescription': '在紙、墨、印六套配色之間切換。',
'settings.general.appearance.paper': '紙墨',
'settings.general.appearance.warmClassic': '經典暖色',
+1
View File
@@ -1444,6 +1444,7 @@ export const zh: Record<TranslationKey, string> = {
// Settings > General
'settings.general.appearanceTitle': '配色主题',
'settings.general.proxyManagedSettingsWarning': '检测到用户配置仅剩 PROXY_MANAGED 代理占位信息,其他设置可能被代理工具覆盖。若这不是你的主动配置,请先关闭 CC Switch 路由接管并从备份恢复 settings.json;主动使用该配置可忽略。',
'settings.general.appearanceDescription': '在纸、墨、印六套配色之间切换。',
'settings.general.appearance.paper': '纸墨',
'settings.general.appearance.warmClassic': '经典暖色',
@@ -88,6 +88,7 @@ export function GeneralSettings() {
setAppMode: setAppModeAction,
uiZoom,
setUiZoom,
proxyManagedSettingsWarning,
} = useSettingsStore()
// Read the theme from the store that owns it. settingsStore keeps a copy for
// its own consumers, but that copy is only refreshed on an explicit setTheme
@@ -616,6 +617,14 @@ export function GeneralSettings() {
return (
<div className="max-w-xl">
{proxyManagedSettingsWarning && (
<div
role="alert"
className="mb-5 rounded-[var(--radius-lg)] border border-[var(--color-warning)] bg-[var(--color-warning-container)] px-3 py-2 text-xs leading-5 text-[var(--color-on-warning-container)]"
>
{t('settings.general.proxyManagedSettingsWarning')}
</div>
)}
{/* No page header here on purpose: the only title it could carry is the nav
label verbatim, with no description to add. The pane opens on its first
section instead. */}
+16
View File
@@ -21,6 +21,7 @@ import {
type ThemeMode,
type UpdateProxyMode,
type UpdateProxySettings,
type UserSettings,
type WebSearchSettings,
} from '../types/settings'
import type { TraceCaptureSettings } from '../types/trace'
@@ -85,6 +86,7 @@ type SettingsStore = {
uiZoom: number
isLoading: boolean
error: string | null
proxyManagedSettingsWarning: boolean
appMode: AppModeConfig
appModeRequiresRestart: boolean
@@ -210,6 +212,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
uiZoom: readStoredAppZoomLevel(),
isLoading: false,
error: null,
proxyManagedSettingsWarning: false,
appMode: {
mode: 'default',
@@ -274,6 +277,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
h5AccessDiagnostics: h5AccessResult.diagnostics,
h5AccessError: h5AccessResult.error,
responseLanguage: typeof userSettings.language === 'string' ? userSettings.language : '',
proxyManagedSettingsWarning: hasProxyManagedOnlyUserSettings(userSettings),
isLoading: false,
error: null,
})
@@ -647,6 +651,18 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
},
}))
export function hasProxyManagedOnlyUserSettings(settings: UserSettings): boolean {
const keys = Object.keys(settings)
if (keys.length !== 1 || keys[0] !== 'env') return false
const env = settings.env
if (typeof env !== 'object' || env === null || Array.isArray(env)) return false
const values = env as Record<string, unknown>
return values.ANTHROPIC_API_KEY === 'PROXY_MANAGED'
|| values.ANTHROPIC_AUTH_TOKEN === 'PROXY_MANAGED'
}
subscribeLocaleChanges((locale) => {
useSettingsStore.setState({ locale })
applyDocumentLocale(locale)
@@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest'
import { hasProxyManagedOnlyUserSettings } from './settingsStore'
describe('proxy-managed user settings warning', () => {
it('recognizes a proxy-only takeover placeholder', () => {
expect(hasProxyManagedOnlyUserSettings({
env: {
ANTHROPIC_BASE_URL: 'http://127.0.0.1:15721',
ANTHROPIC_API_KEY: 'PROXY_MANAGED',
},
})).toBe(true)
})
it('does not flag a normal local model endpoint', () => {
expect(hasProxyManagedOnlyUserSettings({
env: {
ANTHROPIC_BASE_URL: 'http://127.0.0.1:11434',
ANTHROPIC_API_KEY: 'local-model-key',
},
})).toBe(false)
})
it('does not flag a proxy placeholder when other user settings remain', () => {
expect(hasProxyManagedOnlyUserSettings({
env: {
ANTHROPIC_BASE_URL: 'http://127.0.0.1:15721',
ANTHROPIC_AUTH_TOKEN: 'PROXY_MANAGED',
},
language: 'Chinese',
})).toBe(false)
})
})