Skip to content

Gateway: install global proxy dispatcher before channels start#33990

Closed
gallexy-liu wants to merge 7 commits intoopenclaw:mainfrom
gallexy-liu:fix/gateway-global-proxy-dispatcher
Closed

Gateway: install global proxy dispatcher before channels start#33990
gallexy-liu wants to merge 7 commits intoopenclaw:mainfrom
gallexy-liu:fix/gateway-global-proxy-dispatcher

Conversation

@gallexy-liu
Copy link
Copy Markdown

Summary

  • Problem: When a proxy is configured for channels (e.g. channels.discord.proxy/channels.telegram.proxy), third-party libraries like @buape/carbon use globalThis.fetch which ignores HTTP_PROXY env vars and the per-channel proxy config, causing TypeError: fetch failed on restricted networks (e.g. WSL2 + GFW).
  • Why it matters: Discord and Telegram channels silently stop receiving/sending messages if the proxy process is temporarily unavailable or if any channel library bypasses the per-channel proxy.
  • What changed: Added src/gateway/server-global-proxy.ts which installs a ProxyAgent as the global undici dispatcher before channels start, so all globalThis.fetch calls in the gateway process route through the configured proxy. Updated src/telegram/fetch.ts to skip overwriting the dispatcher if a proxy dispatcher is already installed.
  • What did NOT change: Per-channel proxy configuration API, WebSocket proxy via HttpsProxyAgent, REST proxy via per-channel ProxyAgent.

Change Type

  • Bug fix

Scope

  • Gateway / orchestration
  • Integrations

@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram gateway Gateway runtime size: S labels Mar 4, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e83f3d5d33

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/server-global-proxy.ts
Comment thread src/gateway/server-global-proxy.ts Outdated
Comment on lines +31 to +34
for (const acct of Object.values(telegramAccounts)) {
const p = (acct as { proxy?: string })?.proxy?.trim();
if (p) {
return p;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip disabled accounts when picking the global proxy

Proxy selection currently takes the first account proxy regardless of whether that account is disabled, so a stale proxy on channels.telegram.accounts.<id>.proxy or channels.discord.accounts.<id>.proxy can hijack the process-wide dispatcher even when the active account uses a different proxy (or none). In multi-account setups this can break all global fetch traffic based on config order rather than enabled runtime accounts.

Useful? React with 👍 / 👎.

Comment thread src/gateway/server-global-proxy.ts Outdated
}),
);
applied = true;
log.info(`global proxy dispatcher set: ${proxyUrl}${isWsl2 ? " (wsl2 ipv4-only)" : ""}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Redact proxy credentials from startup logs

This info log prints the full proxy URL, which commonly includes inline credentials (for example http://user:pass@proxy:port). As written, authenticated proxy secrets are emitted to console/file logs whenever startup succeeds, creating avoidable credential exposure in environments where logs are shared or persisted.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 4, 2026

Greptile Summary

This PR fixes a proxy routing bug where third-party libraries (e.g., @buape/carbon) using globalThis.fetch would bypass per-channel proxy configuration on restricted networks. The fix installs a global undici ProxyAgent dispatcher before channels start.

Implementation is sound:

  • server-global-proxy.ts: Correctly resolves the first proxy URL across channel configs and installs it as the global dispatcher. Properly applies WSL2 IPv4 workarounds matching the Telegram approach.
  • server-startup.ts: Calls applyGlobalProxyDispatcher at the right point in startup — after config, before channels.
  • src/telegram/fetch.ts: Guards the existing workaround to avoid overwriting the proxy dispatcher.

Security concern:

  • The proxy URL is logged verbatim. If the URL contains embedded credentials, they will be exposed in plaintext to log files and aggregators. Consider sanitizing the URL before logging by stripping userinfo components.

Confidence Score: 4/5

  • Core implementation is functionally correct; one security issue with credential exposure in logs should be addressed.
  • The fix correctly solves the proxy routing problem and is well-integrated into startup order. The code is clear and handles edge cases (WSL2 workarounds, guard to prevent dispatcher overwrite). One security concern remains: proxy URLs with embedded credentials will be logged in plaintext. This is a straightforward fix but should be done before merge to avoid production credential leaks.
  • src/gateway/server-global-proxy.ts — add credential sanitization before logging the proxy URL.

Last reviewed commit: e83f3d5

Comment thread src/gateway/server-global-proxy.ts Outdated
}),
);
applied = true;
log.info(`global proxy dispatcher set: ${proxyUrl}${isWsl2 ? " (wsl2 ipv4-only)" : ""}`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proxy credentials exposed in logs

proxyUrl is logged verbatim at line 117. Proxy URLs can include credentials in their userinfo component, which would be written in plaintext to log files and aggregators.

Consider stripping the userinfo before logging. You can parse the URL with new URL(proxyUrl), clear username and password, then stringify and log the sanitized result:

const url = new URL(proxyUrl);
url.username = "";
url.password = "";
log.info(`global proxy dispatcher set: ${url.toString()}${isWsl2 ? " (wsl2 ipv4-only)" : ""}`);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/server-global-proxy.ts
Line: 117

Comment:
Proxy credentials exposed in logs

`proxyUrl` is logged verbatim at line 117. Proxy URLs can include credentials in their userinfo component, which would be written in plaintext to log files and aggregators.

Consider stripping the userinfo before logging. You can parse the URL with `new URL(proxyUrl)`, clear `username` and `password`, then stringify and log the sanitized result:

```typescript
const url = new URL(proxyUrl);
url.username = "";
url.password = "";
log.info(`global proxy dispatcher set: ${url.toString()}${isWsl2 ? " (wsl2 ipv4-only)" : ""}`);
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f6f46e914a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/server-global-proxy.ts Outdated
Comment on lines +23 to +25
const telegramProxy = cfg.channels?.telegram?.proxy?.trim();
if (telegramProxy) {
return telegramProxy;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip disabled channels when selecting global proxy

resolveFirstChannelProxy returns channel-level proxy URLs without checking whether that channel is enabled, so applyGlobalProxyDispatcher can install a proxy from channels.telegram.proxy (and similarly channels.discord.proxy) even when channels.<name>.enabled is false. In that configuration, all process-wide globalThis.fetch traffic gets forced through a proxy for a channel that is intentionally off, which can break unrelated outbound requests if that proxy is stale or unreachable.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41cde4521d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/server-startup.ts Outdated
// Third-party libs (e.g. @buape/carbon) use globalThis.fetch which does not
// honor HTTP_PROXY env vars; this ensures all outbound fetch() calls route
// through the proxy configured in any channel (e.g. Telegram, Discord).
applyGlobalProxyDispatcher(params.cfg);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip global proxy install when channels are disabled

applyGlobalProxyDispatcher(params.cfg) now runs even when OPENCLAW_SKIP_CHANNELS=1/OPENCLAW_SKIP_PROVIDERS=1, so provider-only/test runs that intentionally skip channels still inherit a channel proxy from channels.telegram.proxy/channels.discord.proxy. In that mode, all process-wide fetch traffic can be forced through a stale or unreachable channel proxy, causing unrelated startup/network failures despite channels being explicitly disabled.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3fa41fe1ee

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/server-global-proxy.ts Outdated
if (telegramAccounts) {
for (const acct of Object.values(telegramAccounts)) {
const account = acct as { enabled?: boolean; proxy?: string };
if (account.enabled !== false) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude tokenless accounts from global proxy selection

This selector only checks enabled, so an account with a proxy but no credentials can still become the process-wide dispatcher. In practice, tokenless Telegram/Discord accounts are not runnable (for example src/telegram/monitor.ts throws when token is empty), so a stale proxy on an unconfigured account can hijack all globalThis.fetch traffic before channels start and break outbound calls for the actually configured accounts.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2780c61e7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// Skip disabled channels (enabled defaults to true when not set)
if (telegram && telegram.enabled !== false) {
// Top-level telegram config has credentials → use its proxy
if (hasTelegramToken(telegram)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve credentials via token resolvers before proxy selection

resolveFirstChannelProxy gates top-level proxy use on hasTelegramToken/hasDiscordToken, which only inspects raw config fields and ignores supported env-token paths (TELEGRAM_BOT_TOKEN, DISCORD_BOT_TOKEN). In configs that rely on env credentials but set channels.telegram.proxy or channels.discord.proxy, this function returns no proxy and applyGlobalProxyDispatcher is skipped, so globalThis.fetch still bypasses the proxy and the connectivity failure this change targets remains reproducible.

Useful? React with 👍 / 👎.

tokenFile?: string;
};
if (account.enabled !== false && hasTelegramToken(account)) {
const p = account.proxy?.trim();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fall back to channel-level proxy for credentialed accounts

The account loops only consider account.proxy, but runtime account config is merged as {...channelBase, ...account} in src/telegram/accounts.ts and src/discord/accounts.ts, so a common setup (channels.<name>.proxy at top level, token on accounts.<id>) is valid at runtime yet invisible here. In that case resolveFirstChannelProxy returns undefined, preventing global dispatcher installation and leaving third-party fetch traffic unproxied.

Useful? React with 👍 / 👎.

@gallexy-liu
Copy link
Copy Markdown
Author

The three failing CI jobs are unrelated to the changes in this PR:

checks-windows / macos: src/plugin-sdk/root-alias.test.ts times out. This file was introduced by upstream commit 5c4ab99 ("Plugins/zalouser: migrate to scoped plugin-sdk imports") which is not yet in my fork's base. The timeout appears to be a pre-existing flaky test on the upstream main branch.
Labeler: Expected failure for fork PRs due to write-permission restrictions on pull_request_target.
My changes only touch [server-global-proxy.ts], [server-startup.ts] and [fetch.ts].The three failing CI jobs are unrelated to the changes in this PR:

@RickyTong1 RickyTong1 mentioned this pull request Mar 12, 2026
20 tasks
@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 13, 2026
@openclaw-barnacle
Copy link
Copy Markdown

Closing due to inactivity.
If you believe this PR should be revived, post in #pr-thunderdome-dangerzone on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram gateway Gateway runtime size: M stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants