Skip to content

[BUG][Mobile] Custom Claude/Anthropic provider ignores "Improve Network Compatibility" (useProxy) toggle → Network Error for custom Anthropic hosts on iOS/Android #3739

@celeste1900

Description

@celeste1900

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:

  1. Add it as a custom OpenAI-compatible provider, enable Improve Network Compatibility → ✅ works.
  2. 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:

  1. UIsrc/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.
  2. Wiringsrc/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).
  3. Modelsrc/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.)
  4. Contrastsrc/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.tsrequest.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 useProxycreateFetchWithProxy 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions