Environment
- Platform: iOS app (v1.20.x) with a system proxy/VPN active. Same code path on Android (Capacitor WebView).
- Provider: a custom provider using the Claude/Anthropic protocol (
CustomClaude), pointed at a self-hosted Anthropic-compatible host that intentionally does not send CORS headers.
- Desktop (Electron) is not affected (
webSecurity: false in src/main/main.ts disables CORS in the renderer).
Summary
For custom providers, the "Improve Network Compatibility" (useProxy) toggle is shown and works for the OpenAI protocol, but is silently ignored by the Claude/Anthropic protocol (CustomClaude). As a result, a custom Anthropic host without CORS headers fails with Network Error on mobile even with the toggle ON, because the request still goes through the WebView fetch (CORS-enforced) instead of the native HTTP layer.
Steps to reproduce (iOS)
Using a custom host that does not return Access-Control-Allow-Origin:
- Add it as a custom OpenAI-compatible provider, enable Improve Network Compatibility → ✅ works.
- Add it as a custom Claude/Anthropic provider, enable Improve Network Compatibility → ❌ still
Network Error.
Same host, same network, toggle ON in both cases — only the Anthropic protocol fails. (Reported to work in an earlier app version, so this appears to be a regression for the user.)
Root cause (verified against current main)
The useProxy toggle is never threaded into CustomClaude, so it can't reach the native-HTTP path:
- UI —
src/renderer/routes/settings/provider/$providerId.tsx: the toggle (<Switch label={t('Improve Network Compatibility')} checked={providerSettings?.useProxy} ...>) is rendered inside {baseInfo.isCustom && (...)}, i.e. for all custom providers, including custom Anthropic.
- Wiring —
src/renderer/packages/model-setting-utils/custom-provider-setting-util.ts:
new CustomOpenAI({ ..., useProxy: settings.useProxy }) ✅ and new CustomOpenAIResponses({ ..., useProxy: settings.useProxy }) ✅
new CustomClaude({ apiHost, apiKey, model, temperature }) ❌ — useProxy is not passed (the CustomClaude Options interface doesn't even declare it).
- Model —
src/shared/providers/definitions/models/custom-claude.ts: getProvider() calls createAnthropic({ baseURL, apiKey, headers }) with no fetch override, so the AI SDK uses the default global fetch → on mobile this is a WebView fetch, subject to CORS. (It does set anthropic-dangerous-direct-browser-access: true, but that only helps when the host itself returns CORS headers; for hosts that don't, the request is still blocked.)
- Contrast —
src/shared/providers/definitions/models/custom-openai.ts declares useProxy?: boolean and routes its fetch through createFetchWithProxy(this.options.useProxy, ...) → src/shared/models/utils/fetch-proxy.ts → request.apiRequest({ ..., useProxy }) → src/renderer/utils/request.ts, where on mobile if (platform.type === 'mobile' && useProxy) return handleMobileRequest(...) → src/renderer/utils/mobile-request.ts → native CapacitorHttp / StreamHttp, which bypasses WebView CORS. That's exactly why the toggle works for custom OpenAI but not custom Claude.
The custom model classes (CustomOpenAI, CustomClaude, ...) were migrated in the Provider System Refactor (tasks/prd-provider-system-refactor.md); CustomOpenAI got the useProxy → createFetchWithProxy wiring, CustomClaude did not. The built-in Claude provider (definitions/claude.ts) similarly only sets a customFetch for the OAuth branch.
Suggested fix
Add useProxy?: boolean to CustomClaude's Options, pass useProxy: settings.useProxy when constructing it in custom-provider-setting-util.ts, and route createAnthropic({ fetch: createFetchWithProxy(this.options.useProxy, this.dependencies) }) — mirroring CustomOpenAI. (Same gap likely applies to CustomGemini and the built-in Claude provider.)
Related
#1791 (iOS local network / Ollama), #2414 (forced HTTPS on mobile).
Environment
CustomClaude), pointed at a self-hosted Anthropic-compatible host that intentionally does not send CORS headers.webSecurity: falseinsrc/main/main.tsdisables CORS in the renderer).Summary
For custom providers, the "Improve Network Compatibility" (
useProxy) toggle is shown and works for the OpenAI protocol, but is silently ignored by the Claude/Anthropic protocol (CustomClaude). As a result, a custom Anthropic host without CORS headers fails withNetwork Erroron mobile even with the toggle ON, because the request still goes through the WebViewfetch(CORS-enforced) instead of the native HTTP layer.Steps to reproduce (iOS)
Using a custom host that does not return
Access-Control-Allow-Origin:Network Error.Same host, same network, toggle ON in both cases — only the Anthropic protocol fails. (Reported to work in an earlier app version, so this appears to be a regression for the user.)
Root cause (verified against current
main)The
useProxytoggle is never threaded intoCustomClaude, so it can't reach the native-HTTP path:src/renderer/routes/settings/provider/$providerId.tsx: the toggle (<Switch label={t('Improve Network Compatibility')} checked={providerSettings?.useProxy} ...>) is rendered inside{baseInfo.isCustom && (...)}, i.e. for all custom providers, including custom Anthropic.src/renderer/packages/model-setting-utils/custom-provider-setting-util.ts:new CustomOpenAI({ ..., useProxy: settings.useProxy })✅ andnew CustomOpenAIResponses({ ..., useProxy: settings.useProxy })✅new CustomClaude({ apiHost, apiKey, model, temperature })❌ —useProxyis not passed (theCustomClaudeOptionsinterface doesn't even declare it).src/shared/providers/definitions/models/custom-claude.ts:getProvider()callscreateAnthropic({ baseURL, apiKey, headers })with nofetchoverride, so the AI SDK uses the default globalfetch→ on mobile this is a WebView fetch, subject to CORS. (It does setanthropic-dangerous-direct-browser-access: true, but that only helps when the host itself returns CORS headers; for hosts that don't, the request is still blocked.)src/shared/providers/definitions/models/custom-openai.tsdeclaresuseProxy?: booleanand routes its fetch throughcreateFetchWithProxy(this.options.useProxy, ...)→src/shared/models/utils/fetch-proxy.ts→request.apiRequest({ ..., useProxy })→src/renderer/utils/request.ts, where on mobileif (platform.type === 'mobile' && useProxy) return handleMobileRequest(...)→src/renderer/utils/mobile-request.ts→ nativeCapacitorHttp/StreamHttp, which bypasses WebView CORS. That's exactly why the toggle works for custom OpenAI but not custom Claude.The custom model classes (
CustomOpenAI,CustomClaude, ...) were migrated in the Provider System Refactor (tasks/prd-provider-system-refactor.md);CustomOpenAIgot theuseProxy→createFetchWithProxywiring,CustomClaudedid not. The built-inClaudeprovider (definitions/claude.ts) similarly only sets acustomFetchfor the OAuth branch.Suggested fix
Add
useProxy?: booleantoCustomClaude'sOptions, passuseProxy: settings.useProxywhen constructing it incustom-provider-setting-util.ts, and routecreateAnthropic({ fetch: createFetchWithProxy(this.options.useProxy, this.dependencies) })— mirroringCustomOpenAI. (Same gap likely applies toCustomGeminiand the built-inClaudeprovider.)Related
#1791 (iOS local network / Ollama), #2414 (forced HTTPS on mobile).