Skip to content

fix(gateway/discord): respect env proxy vars and prevent ECONNRESET#47446

Closed
winter-loo wants to merge 2 commits into
openclaw:mainfrom
winter-loo:fix/gateway-discord-env-proxy
Closed

fix(gateway/discord): respect env proxy vars and prevent ECONNRESET#47446
winter-loo wants to merge 2 commits into
openclaw:mainfrom
winter-loo:fix/gateway-discord-env-proxy

Conversation

@winter-loo

Copy link
Copy Markdown

Summary

  • Bootstrap global EnvHttpProxyAgent in gateway startup so Node's fetch() honors https_proxy/HTTP_PROXY env vars — fixes Discord REST calls (command deployment, bot identity fetch) timing out behind local proxies
  • Discord gateway plugin now falls back to env proxy vars for WebSocket connections when no explicit channels.discord.proxy is configured
  • Disable keepAlive on all proxy ProxyAgent instances to prevent ECONNRESET from local proxies (Clash, Surge, etc.) that close HTTP CONNECT tunnels after one request

Test plan

  • pnpm test -- src/infra/net/undici-global-dispatcher.test.ts passes (10/10)
  • pnpm test -- extensions/discord/src/monitor/provider.proxy.test.ts extensions/discord/src/monitor/provider.rest-proxy.test.ts passes (8/8)
  • Manual: verify Discord gateway connects and responds to interactions behind a local proxy with https_proxy set

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord cli CLI command changes size: XS labels Mar 15, 2026
@greptile-apps

greptile-apps Bot commented Mar 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two proxy-related reliability issues for Discord gateway connections: it bootstraps a global EnvHttpProxyAgent so Node's fetch() honours https_proxy/HTTP_PROXY env vars, and disables keepAlive on all ProxyAgent / EnvHttpProxyAgent instances to prevent ECONNRESET from local proxies (Clash, Surge, etc.) that close HTTP CONNECT tunnels after a single request.

  • run.ts: adds ensureGlobalUndiciEnvProxyDispatcher() before ensureGlobalUndiciStreamTimeouts() so that the timeout options wrap the already-active EnvHttpProxyAgent (ordering comment is clear and correct).
  • undici-global-dispatcher.ts: keepAlive: false is added to both the initial proxy bootstrap and the timeout-wrapped EnvHttpProxyAgent. Minor: the spread on line 126 is { keepAlive: false, ...connect } — if resolveConnectOptions were ever extended with a keepAlive property, it would silently override the intent; { ...connect, keepAlive: false } would be safer.
  • gateway-plugin.ts: falls back to resolveEnvHttpProxyUrl("https") for WebSocket and gateway REST fetches when no explicit channels.discord.proxy is set. Per-request dispatcher overrides the global EnvHttpProxyAgent, so there is no double-proxy routing.
  • rest-fetch.ts: ProxyAgent constructor updated to pass { uri, connect: { keepAlive: false } }.
  • Test files: mocks updated to accept the new constructor signature; resolveEnvHttpProxyUrl is mocked to undefined in existing gateway tests to isolate them from the env fallback.

Confidence Score: 4/5

  • Safe to merge; one minor defensive-coding concern with spread order that poses no current risk.
  • The changes are focused, well-tested (10 + 8 unit tests), and the ordering of bootstrap calls is correct. The only concern is the { keepAlive: false, ...connect } spread order in ensureGlobalUndiciStreamTimeouts — because connect currently never includes keepAlive, there is no active bug, but the ordering is fragile if resolveConnectOptions is ever extended.
  • src/infra/net/undici-global-dispatcher.ts line 126 — spread order of proxyConnect.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/infra/net/undici-global-dispatcher.ts
Line: 126

Comment:
**Spread order may allow `connect` to override `keepAlive: false`**

The intent of `proxyConnect` is to always enforce `keepAlive: false` to prevent ECONNRESET from local proxies. However, using `{ keepAlive: false, ...connect }` means any `keepAlive` property that `connect` carries (e.g., if `resolveConnectOptions` is ever extended) would override the `false` value. For the invariant to hold unconditionally, `keepAlive: false` should be the last spread:

```suggestion
      const proxyConnect = { ...connect, keepAlive: false };
```

Currently `resolveConnectOptions` never returns `keepAlive`, so there is no active bug — but the defensive order guards against future regressions.

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

Last reviewed commit: 3e8943e

const connect = resolveConnectOptions(autoSelectFamily);
try {
if (kind === "env-proxy") {
const proxyConnect = { keepAlive: false, ...connect };

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.

Spread order may allow connect to override keepAlive: false

The intent of proxyConnect is to always enforce keepAlive: false to prevent ECONNRESET from local proxies. However, using { keepAlive: false, ...connect } means any keepAlive property that connect carries (e.g., if resolveConnectOptions is ever extended) would override the false value. For the invariant to hold unconditionally, keepAlive: false should be the last spread:

Suggested change
const proxyConnect = { keepAlive: false, ...connect };
const proxyConnect = { ...connect, keepAlive: false };

Currently resolveConnectOptions never returns keepAlive, so there is no active bug — but the defensive order guards against future regressions.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/net/undici-global-dispatcher.ts
Line: 126

Comment:
**Spread order may allow `connect` to override `keepAlive: false`**

The intent of `proxyConnect` is to always enforce `keepAlive: false` to prevent ECONNRESET from local proxies. However, using `{ keepAlive: false, ...connect }` means any `keepAlive` property that `connect` carries (e.g., if `resolveConnectOptions` is ever extended) would override the `false` value. For the invariant to hold unconditionally, `keepAlive: false` should be the last spread:

```suggestion
      const proxyConnect = { ...connect, keepAlive: false };
```

Currently `resolveConnectOptions` never returns `keepAlive`, so there is no active bug — but the defensive order guards against future regressions.

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

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

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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3e8943e82b

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 128 to +130
bodyTimeout: timeoutMs,
headersTimeout: timeoutMs,
...(connect ? { connect } : {}),
connect: proxyConnect,

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.

P1 Badge Set EnvHttpProxyAgent tunnel options via proxyTls

EnvHttpProxyAgent does not apply connect settings to proxied HTTPS tunnels (those settings only affect the no-proxy/direct agent), so keepAlive: false here will not stop CONNECT tunnel reuse. This repo already codifies that behavior in extensions/telegram/src/fetch.ts and extensions/telegram/src/fetch.env-proxy-runtime.test.ts, so users behind local proxies that close reused tunnels can still hit ECONNRESET unless this is passed in proxyTls.

Useful? React with 👍 / 👎.

}
try {
const agent = new ProxyAgent(proxy);
const agent = new ProxyAgent({ uri: proxy, connect: { keepAlive: false } });

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.

P1 Badge Set ProxyAgent keep-alive policy via proxyTls

ProxyAgent uses proxyTls for proxy-connection transport options, so passing keepAlive: false under connect leaves proxy tunnel reuse unchanged. In explicit Discord proxy setups, repeated REST calls can still reuse stale CONNECT tunnels and continue surfacing ECONNRESET from proxies that close tunnels after a single request.

Useful? React with 👍 / 👎.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime extensions: google-gemini-cli-auth Extension: google-gemini-cli-auth size: S and removed size: XS labels Mar 15, 2026
@winter-loo
winter-loo force-pushed the fix/gateway-discord-env-proxy branch from 01c1c0f to e7cbad7 Compare March 15, 2026 17:31
@openclaw-barnacle openclaw-barnacle Bot removed the extensions: google-gemini-cli-auth Extension: google-gemini-cli-auth label Mar 15, 2026

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

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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7cbad7271

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}): GatewayPlugin {
const intents = resolveDiscordGatewayIntents(params.discordConfig?.intents);
const proxy = params.discordConfig?.proxy?.trim();
const proxy = params.discordConfig?.proxy?.trim() || resolveEnvHttpProxyUrl("https");

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.

P1 Badge Honor NO_PROXY when deriving Discord proxy from env

Falling back to resolveEnvHttpProxyUrl("https") here forces Discord gateway traffic into an explicit HttpsProxyAgent/ProxyAgent whenever HTTPS_PROXY is set, but this path does not evaluate NO_PROXY/no_proxy exclusions. In split-proxy environments (for example, global proxy set but discord.com bypassed via NO_PROXY), the gateway now incorrectly routes through the proxy and can fail to connect or fetch gateway metadata, even though env-proxy semantics should bypass that host.

Useful? React with 👍 / 👎.

winter-loo and others added 2 commits March 18, 2026 04:52
…n proxy tunnels

Gateway startup now bootstraps the global undici EnvHttpProxyAgent so
Node's fetch() honors https_proxy/HTTP_PROXY. The Discord gateway plugin
also falls back to env proxy vars for its WebSocket connection when no
explicit channels.discord.proxy is configured. All proxy ProxyAgent
instances disable keepAlive to avoid ECONNRESET from local proxies
(Clash, Surge, etc.) that close CONNECT tunnels after one request.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@winter-loo
winter-loo force-pushed the fix/gateway-discord-env-proxy branch from e7cbad7 to cb4d03b Compare March 17, 2026 21:12

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

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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb4d03bb86

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { ProxyAgent, fetch as undiciFetch } from "undici";
import WebSocket from "ws";
import { resolveEnvHttpProxyUrl } from "../../../../src/infra/net/proxy-env.js";

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.

P1 Badge Stop importing core src files from Discord extension entrypoint

This new static import makes the bundled Discord extension depend on ../../../../src/..., but npm installs package extensions/ and dist/ (not src/), and bundled-plugin discovery loads from the package-root extensions/ tree when dist-runtime/extensions is absent; in that common install path, resolving this module throws ERR_MODULE_NOT_FOUND before the Discord plugin can initialize. This means Discord users on normal npm-distributed builds can fail at startup as soon as the extension is loaded.

Useful? React with 👍 / 👎.

@winter-loo

Copy link
Copy Markdown
Author

Rebased onto current main, but CI is still red due to failures that do not appear to come from this branch’s diff.

Current branch diff is limited to:

  • extensions/discord/src/monitor/gateway-plugin.ts
  • extensions/discord/src/monitor/provider.proxy.test.ts
  • extensions/discord/src/monitor/provider.rest-proxy.test.ts
  • extensions/discord/src/monitor/rest-fetch.ts
  • src/cli/gateway-cli/run.ts
  • src/gateway/node-command-policy.ts
  • src/gateway/server-methods/nodes.ts
  • src/infra/net/undici-global-dispatcher.test.ts
  • src/infra/net/undici-global-dispatcher.ts

Current failing checks:

  • check
  • checks (node, contracts, pnpm test:contracts)
  • checks (node, test, 2, 2, pnpm canvas:a2ui:bundle && pnpm test)
  • secrets

What I found:

  • checks (node, contracts, pnpm test:contracts) is annotated on src/channels/plugins/contracts/inbound.contract.test.ts, not on a file touched by this PR.
  • Local reproduction of pnpm test:contracts also hits unrelated breakage in extensions/feishu/index.ts due to duplicated feishuPlugin exports.
  • Local pnpm check points at unrelated repo-state/type issues outside this PR’s diff, including extensions/feishu/index.ts and src/channels/plugins/contracts/registry.ts.
  • secrets is failing in the repo-wide Audit production dependencies step, and this PR does not modify dependency manifests or workflow files.

I’m happy to rerun once main is green again, or split out a separate blocker-fix PR if maintainers want that.

@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Keep open: this is a plausible Discord proxy bug fix, but the branch is not merge-ready because it crosses the plugin boundary, ignores target NO_PROXY semantics, changes bot-token proxy egress through a raw Undici path, and lacks real Discord proxy proof.

Canonical path: Close this stale PR. The latest review rated it F, the branch still lacks merge-ready proof, and there has been no human follow-up after the durable review.

So I’m closing this here because the remaining work is already tracked in the canonical issue.

Review details

Best possible solution:

Close this stale PR. The latest review rated it F, the branch still lacks merge-ready proof, and there has been no human follow-up after the durable review.

Do we have a high-confidence way to reproduce the issue?

Yes for the patch defects: source inspection plus Undici 8.3.0 source show the plugin-boundary import, NO_PROXY bypass, raw token proxy path, and ineffective keep-alive placement. No high-confidence live reproduction exists for the original Discord local-proxy ECONNRESET path because manual proof remains unchecked.

Is this the best way to solve the issue?

No. The best fix should be rebuilt on current main using SDK proxy helpers and guarded Discord metadata policy rather than merging a stale raw Undici proxy path from extension code.

Security review:

Security review needs attention: The diff changes auth-bearing Discord proxy routing and bypasses the guarded metadata fetch path, so the security review is not cleared.

  • [high] Raw proxy path can carry Discord bot tokens — extensions/discord/src/monitor/gateway-plugin.ts:288
    The PR sends /gateway/bot through raw undiciFetch with a ProxyAgent, carrying the Authorization header outside the existing fetchWithSsrFGuard hostname allowlist and explicit dispatcher policy.
    Confidence: 0.9
  • [medium] NO_PROXY exclusions are ignored for env proxy fallback — extensions/discord/src/monitor/gateway-plugin.ts:271
    Selecting only resolveEnvHttpProxyUrl("https") before creating explicit proxy agents can send Discord traffic through a proxy the operator excluded with NO_PROXY, including token-bearing metadata requests.
    Confidence: 0.88

What I checked:

  • stale F-rated PR: PR was opened 2026-03-15T15:51:38Z, is older than 60 days, and the latest review rated it F.
  • proof blocker: real behavior proof is missing and proof tier is F, so this branch is not merge-ready without contributor follow-up.
  • no human follow-up: live comments and timeline hydrated by apply contain no non-automation activity after the ClawSweeper review.

Likely related people:

  • steipete: Recent main history shows repeated Discord gateway startup, proxy, metadata, and SDK boundary changes in the affected files. (role: recent area contributor; confidence: high; commits: 827b0de0ce74, c3c7c2df6f62, 3a6d50deb3bd; files: extensions/discord/src/monitor/gateway-plugin.ts, extensions/discord/src/monitor/rest-fetch.ts, src/plugin-sdk/fetch-runtime.ts)
  • vincentkoc: Merged Discord proxy and gateway metadata hardening introduced the current guarded direct metadata behavior this PR would change. (role: security hardening contributor; confidence: high; commits: 718dffd2f2ea, f6504ceb1d2a, 50e1eb56d761; files: extensions/discord/src/monitor/gateway-plugin.ts, extensions/discord/src/monitor/provider.proxy.test.ts)
  • jesse-merhi: Recent proxy runtime work added managed HTTPS proxy CA trust and hardened Undici proxy dispatcher helpers used by the current SDK path. (role: proxy runtime contributor; confidence: medium; commits: 7c2425a5186d; files: src/infra/net/undici-runtime.ts, src/infra/net/undici-global-dispatcher.ts, extensions/discord/src/monitor/rest-fetch.ts)
  • shakkernerd: Recent tests tightened Discord gateway metadata and proxy assertions around the affected behavior. (role: adjacent test contributor; confidence: medium; commits: 4766ad6b06b6, fb072ea8ca85; files: extensions/discord/src/monitor/provider.proxy.test.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 6981051682f2.

@clawsweeper clawsweeper Bot added the P1 High-priority user-facing bug, regression, or broken workflow. label May 17, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 17, 2026
@clawsweeper clawsweeper Bot added impact:security Security boundary, credential, authz, sandbox, or sensitive-data risk. impact:message-loss Channel message delivery can be lost, duplicated, or misrouted. impact:auth-provider Auth, provider routing, model choice, or SecretRef resolution may break. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed impact:security Security boundary, credential, authz, sandbox, or sensitive-data risk. impact:message-loss Channel message delivery can be lost, duplicated, or misrouted. labels May 17, 2026
@clawsweeper clawsweeper Bot added merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. and removed impact:auth-provider Auth, provider routing, model choice, or SecretRef resolution may break. labels May 18, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@clawsweeper clawsweeper Bot added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. labels May 20, 2026
@clawsweeper

clawsweeper Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

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

Labels

channel: discord Channel integration: discord cli CLI command changes gateway Gateway runtime merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: XS status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant