Skip to content

fix(bots): apply the configured network proxy to Client-owned bot traffic - #5159

Draft
ying-hua wants to merge 1 commit into
apache:mainfrom
ying-hua:fix/5091-bot-proxy
Draft

fix(bots): apply the configured network proxy to Client-owned bot traffic#5159
ying-hua wants to merge 1 commit into
apache:mainfrom
ying-hua:fix/5091-bot-proxy

Conversation

@ying-hua

@ying-hua ying-hua commented Sep 10, 2026

Copy link
Copy Markdown

Summary

setActiveProxy had no production caller anywhere in the repository, so resolveActiveProxy() always returned null and the bot bridges' proxiedFetch never built a proxy dispatcher. Every bot request went direct whatever the Network settings said. On a blocked network that surfaces as the Fetch timeout in the report, and it explains why enabling TUN was the only thing that worked — TUN intercepts below the application, so it needs no cooperation from this seam.

Model execution was never affected: it resolves the proxy inside the Host and injects a transport per connection. The bot bridges cannot do that. BotRegistry is constructed in the Client process, the local Host is a separate spawned process, and the resolved proxy — including its credential — only exists Host-side. That is the actual gap; the protocol exposed only network-proxy.test.

So this adds network-proxy.resolve, served by HostNetworkProxyCoordinator from the existing resolveNetworkProxyExecution() and toRuntimePolicyProxy(), and applies the result in the Client at Host registration and whenever a settings patch writes the proxy policy.

Two decisions worth flagging:

  • A non-local Host's policy is never adopted. The bot bridges dial out from the Client machine, so a remote or environment Host describes the wrong network. Those profiles are not even asked — the Client stays direct rather than guessing.
  • The first resolution can lose a race with Host readiness. Rather than settling on direct until the user next edits the proxy, a failed resolution keeps the last applied value and retries on a bounded 1s/5s/15s budget.

RUNTIME_HOST_COMPATIBILITY_EPOCH goes to 141. Two builds can share an epoch and differ in operations, so without the bump a Client could reach an already-running older local Host that cannot answer the new operation — and a Client cannot tell that refusal apart from a proxy that is genuinely unset.

The fix covers every bot platform, not just Telegram — telegram, discord, qq, dingtalk and wechat bridges, credential verification in bot-test.ts, and the Desktop onboarding and QQ scan-login paths all share proxiedFetch.

Fixes #5091

Verification

Reproduced and verified on Windows 11 with Clash, TUN off and system proxy on at 127.0.0.1:7897. HTTP_PROXY/HTTPS_PROXY were stripped from the process to match a normal desktop launch.

Driving the real chain end to end — Host coordinator → protocol codec → applier → setActiveProxy → the bots' proxiedFetch → a live request to api.telegram.org:

env HTTPS_PROXY = (unset)
A. before any refresh (today's shipped behaviour): FAILED in 10010ms -> Fetch timeout
B. after refresh with a remote Host:               FAILED in 10009ms -> Fetch timeout
B. after refresh with a local Host:                HTTP 200 in 2567ms

A is main today, and its error string and 10s duration match the report. The middle line confirms the non-local gate holds. Only the local Host applies the proxy.

Checks run locally:

  • npm run lint, npm run format:check, npm run check:asf-headers — clean
  • npm run typecheck — clean across all workspaces
  • npx knip — no new findings
  • Affected suites, 117 passing: the new network-proxy-coordinator and client-network-proxy suites, plus runtime-host-settings-ipc-main, proxied-fetch and protocol

Not run: the full workspace suite and the Desktop E2E specs.

Two Runtime Host suites do not pass on this machine, and neither is evidence about this change:

  • handshake-compatibility.test.ts fails 4 of 6, identically on unmodified upstream/main (RuntimeHostTransportError: read_eof).
  • host-kernel.test.ts is unstable here regardless of the change: unmodified upstream/main fails 4 of 81, and the failing set differs between runs (write EPIPE, Runtime Host did not enter composition, shutdown did not cleanly close every resource). This branch failed 1–2 of 81 across runs — a subset of what clean main produces — so I cannot attribute or exonerate individual tests here, only report that the suite is flaky on this machine before the change.

framed-transport.test.ts passes 6 of 6. These look like the local Windows platform-fixture and cleanup problems tracked in #5059; someone on Linux or macOS should confirm both suites.

Security

network-proxy.resolve sends the proxy credential from the Host to the Client. That is new, and SECURITY.md calls out proxiedFetch specifically, so it deserves explicit review. The reasoning: a Client that runs its own outbound traffic cannot dial an authenticated proxy without the secret, and the alternative — a separate Client-side proxy setting — duplicates configuration the user has already entered. The failure path returns a fixed message rather than the underlying error, which can carry credential material, and there is a test for that.

If you would rather keep the secret Host-side, the alternative is a dedicated Client-owned bot proxy setting (option 1 in the report). Happy to switch.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude Code — root-cause investigation, the implementation, and the tests, under my direction and review. Commits carry Generated-by: Claude Code.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

…ffic

`setActiveProxy` had no production caller, so `resolveActiveProxy()` always
returned null and the bot bridges' `proxiedFetch` never built a proxy
dispatcher. Every bot request went direct whatever Network settings said,
which on a blocked network surfaces as the `Fetch timeout` in apache#5091.

Model execution is unaffected because it resolves the proxy inside the Host
and injects a transport per connection. The bot bridges cannot: `BotRegistry`
is constructed in the Client process, and the resolved proxy — including its
credential — only exists Host-side.

Add `network-proxy.resolve` so the Host can serve that resolved proxy, and
apply it in the Client at Host registration and whenever a settings patch
writes the proxy policy. A non-local Host describes a different machine's
network, so its policy is never adopted; the Client stays direct instead.

Refs apache#5091

Generated-by: Claude Code
@github-actions github-actions Bot added the effort/L Under 1000 readable lines label Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/L Under 1000 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telegram Bot listener times out with TUN disabled; proxy environment variables restore message reception

1 participant