fix(sandbox): rewrite #2109 proxy fix as http.request wrapper - #2323
fix(sandbox): rewrite #2109 proxy fix as http.request wrapper#2323lcsmontiel wants to merge 4 commits into
Conversation
PR NVIDIA#2110's axios-only Module._load preload never fired at runtime: 1. nemoclaw-blueprint/scripts/ is excluded from the optimized sandbox build context (src/lib/sandbox-build-context.ts), so axios-proxy-fix.js was not baked into the sandbox image. 2. Adding scripts/ to the build context cache-busts the `COPY nemoclaw-blueprint/` Dockerfile layer and hangs npm ci in the k3s Docker-in-Docker build, so the delivery gap cannot be closed by expanding the context. 3. Even if the file had reached the image, intercepting require('axios') via Module._load cannot patch follow-redirects + proxy-from-env bundled as ESM in OpenClaw's dist/http-Bh-HtMAg.js — there are no require() calls to intercept. The Bot Connector reply path uses the bundled code. Replace with an http.request() wrapper — the lowest common denominator every HTTP library bottoms out at. Detect FORWARD-mode requests (hostname = proxy IP, path = full https:// URL) and rewrite them to https.request() against the real target, letting NODE_USE_ENV_PROXY handle the CONNECT tunnel correctly. Works for any HTTP client, including bundled ESM that makes no require() calls. Delivery: - nemoclaw-blueprint/scripts/http-proxy-fix.js — canonical source for review and tests. - scripts/nemoclaw-start.sh embeds the same JS inline via a heredoc, writes it to /tmp/nemoclaw-http-proxy-fix.js through emit_sandbox_sourced_file (root:root 444, symlink-safe), and loads it via NODE_OPTIONS=--require. No changes to sandbox-build-context. - test/http-proxy-fix-sync.test.ts enforces byte-for-byte equality between the heredoc and the canonical file, so future edits cannot silently diverge. - validate_tmp_permissions is invoked with the new path on both the root and non-root boot paths (the fix JS is a trust-boundary file — tampering would inject arbitrary code into every Node process via NODE_OPTIONS). Because the content ships inside nemoclaw-start.sh rather than as a separately-deployed file, the fix fires on the very first sandbox boot with no post-onboard deploy + restart dance required. Verified end-to-end on 2026-04-23: EC2 t3.large (ca-central-1), NemoClaw v0.0.22 + OpenShell v0.0.29, Node 22.22.1. Direct axios.get('https://clawhub.ai') returns 200 inside the sandbox; full Teams -> ALB -> OpenClaw -> LiteLLM/Bedrock -> Bot Connector -> Teams round-trip succeeds. No `FORWARD rejected` entries in OpenShell network logs. Comparison table and reproduction steps posted in the PR description. Scope: - Fixes the NVIDIA#2109 regression class (axios / follow-redirects / proxy-from-env FORWARD-mode rewrites on NODE_USE_ENV_PROXY=1). - Does NOT fix NVIDIA#1570 (Discord WebSocket via the ws library). That bug sits at a different layer — EnvHttpProxyAgent's FORWARD-vs- CONNECT decision for Upgrade: websocket requests — and needs the agent-swap treatment that NVIDIA#2296 applies. The http.request wrapper in this PR cannot safely handle that case (it would re-enter the same faulty agent logic). - Does NOT modify sandbox-build-context.ts. Removes the superseded nemoclaw-blueprint/scripts/axios-proxy-fix.js and updates the existing regression tests in service-env.test.ts to the new variable name (_PROXY_FIX_SCRIPT). Closes NVIDIA#2109.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughRemoves an axios-specific preload and adds a new preload that monkey-patches Node's Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant NodeRuntime as Node
participant Env as EnvVars
participant ProxyHost as Proxy
participant Destination as RemoteHost
Note over NodeRuntime,Env: On startup, preload (`http-proxy-fix.js`) is required if NODE_USE_ENV_PROXY=1
Client->>NodeRuntime: call http.request(options)
NodeRuntime->>NodeRuntime: patched http.request inspects options and Env.PROXY_HOST
alt options.host matches Env.PROXY_HOST and options.path startsWith "https://"
NodeRuntime->>NodeRuntime: parse target URL from options.path
NodeRuntime->>Destination: call https.request(parsedTarget, options, cb)
Destination-->>NodeRuntime: response
NodeRuntime-->>Client: deliver response via callback
else
NodeRuntime->>ProxyHost: call original http.request(options)
ProxyHost-->>NodeRuntime: response
NodeRuntime-->>Client: deliver response
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@nemoclaw-blueprint/scripts/http-proxy-fix.js`:
- Around line 61-82: The code calls new URL(options.path) without handling URL
parsing errors which can throw for malformed values; wrap the new
URL(options.path) call in a try/catch (inside the if that checks
options.hostname === proxyHost and options.path.startsWith('https://')) and on
catch simply fall back to returning origRequest.apply(http, arguments) (or
otherwise return origRequest) so the process doesn't crash; ensure
callback/return behavior matches the existing branch and keep references to
options.path, proxyHost, new URL, and origRequest when locating the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: df788072-7fbc-47dd-9a12-ce0dff26bbe0
📒 Files selected for processing (5)
nemoclaw-blueprint/scripts/axios-proxy-fix.jsnemoclaw-blueprint/scripts/http-proxy-fix.jsscripts/nemoclaw-start.shtest/http-proxy-fix-sync.test.tstest/service-env.test.ts
💤 Files with no reviewable changes (1)
- nemoclaw-blueprint/scripts/axios-proxy-fix.js
|
@BenediktSchackenberg @ericksoa You may want to take a look at this PR. Thank you. |
Wrap the `new URL(options.path)` call in a try/catch so that a
malformed path value (which passes the `startsWith('https://')` check
but still fails URL parsing) falls back to the original http.request
instead of crashing the Node process.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@nemoclaw-blueprint/scripts/http-proxy-fix.js`:
- Around line 50-54: Change the unused catch binding name from e to _e wherever
it appears: update the try/catch blocks that parse proxyUrl (the catch
surrounding new URL(proxyUrl) that assigns proxyHost) and the other catch block
around lines 67-70 to use catch (_e) and also update the embedded heredoc in
scripts/nemoclaw-start.sh where the same catch binding is duplicated so both JS
(http-proxy-fix.js) and the shell-embedded JS use the prefixed _e name to
satisfy the unused-variable guideline.
- Around line 72-83: The current FORWARD-mode branch recreates the https.request
options object and drops important fields (signal, agent, auth, ca/cert/key,
rejectUnauthorized, etc.); instead, clone the original options and overwrite
only the proxy-specific fields so existing options are preserved: use
Object.assign({}, options, { method: options.method||'GET', hostname:
target.hostname, host: target.hostname, port: target.port||443, path:
target.pathname+target.search, protocol: 'https:' }) and pass that to
https.request (the call around https.request and variables options/target/method
must be updated) so AbortController, custom agents, TLS and auth fields remain
intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: f000a9b2-7657-4507-834f-35cda948a2dc
📒 Files selected for processing (2)
nemoclaw-blueprint/scripts/http-proxy-fix.jsscripts/nemoclaw-start.sh
Address CodeRabbit review feedback on PR.
The FORWARD-mode branch previously constructed a fresh options object
with only {method, hostname, host, port, path, protocol, headers,
timeout}, silently dropping caller-supplied fields that can matter for
correctness:
- signal — AbortController, used by modern axios/fetch for
cancellation. Dropping it meant user-initiated aborts would not
propagate to the rewritten https.request, leaving the request
running after the caller thought it was cancelled.
- TLS: ca, cert, key, passphrase, rejectUnauthorized — custom trust
anchors or mTLS settings. Uncommon in the FORWARD path but not
impossible.
- auth — Basic-auth credentials for the target origin.
- lookup, family, localAddress, maxHeaderSize, insecureHTTPParser —
per-request network/parser tuning.
Switch to Object.assign({}, options, { ...proxy-routing-fields })
which clones the caller's options and overwrites only the fields we
explicitly need to change (method default, hostname/host/port/path/
protocol). Everything else is carried over verbatim.
Mirror the edit in the inline heredoc in scripts/nemoclaw-start.sh so
the canonical file and the embedded copy remain byte-identical; the
http-proxy-fix-sync test enforces this.
Address CodeRabbit review feedback (minor, PR NVIDIA#2323). Both `catch (e)` bindings in http-proxy-fix.js are unused — one in the proxy URL parse (falls through silently) and one in the FORWARD-path new URL guard (returns the original request). Rename to `catch (_e)` to satisfy the project's "unused variables must be prefixed with _" convention. Mirror the edit in the inline heredoc in scripts/nemoclaw-start.sh so the canonical file and the embedded copy remain byte-identical; the http-proxy-fix-sync test enforces this.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/nemoclaw-start.sh`:
- Around line 878-882: The proxy matcher currently only checks options.hostname
against proxyHost so requests that pass { host, port, path } slip through;
update the conditional that uses options.hostname (and the embedded heredoc
copy) to also accept options.host (e.g., check (options.hostname === proxyHost
|| options.host === proxyHost)) before testing
options.path.startsWith('https://') so host-only request options are rewritten
like hostname-based ones; ensure the same change is made in the corresponding
embedded heredoc to keep the two files byte-for-byte identical.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 1339cf9d-d779-4f6d-9abd-47b64cb94215
📒 Files selected for processing (2)
nemoclaw-blueprint/scripts/http-proxy-fix.jsscripts/nemoclaw-start.sh
🚧 Files skipped from review as they are similar to previous changes (1)
- nemoclaw-blueprint/scripts/http-proxy-fix.js
| if ( | ||
| options.hostname === proxyHost && | ||
| options.path && | ||
| options.path.startsWith('https://') | ||
| ) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Node.js http.request() docs =="
curl -fsSL https://nodejs.org/api/http.html | rg -n -C2 'hostname|host'
echo
echo "== Current matcher in scripts/nemoclaw-start.sh =="
sed -n '874,905p' scripts/nemoclaw-start.sh
echo
echo "== Current proxy-fix tests =="
sed -n '1,220p' test/http-proxy-fix-sync.test.ts
rg -n -C2 'host|hostname|http\.request|https\.request' testRepository: NVIDIA/NemoClaw
Length of output: 50372
🏁 Script executed:
# Read the actual matcher code
sed -n '874,905p' scripts/nemoclaw-start.shRepository: NVIDIA/NemoClaw
Length of output: 1174
🏁 Script executed:
# Check if canonical http-proxy-fix.js exists and read it
find . -name "http-proxy-fix.js" -o -name "http-proxy-fix*.js" | head -20Repository: NVIDIA/NemoClaw
Length of output: 105
🏁 Script executed:
# Look for test cases using 'host' option in HTTP requests
rg -n "host:\s*['\"]" test/http-proxy-fix-sync.test.ts | head -20Repository: NVIDIA/NemoClaw
Length of output: 41
🏁 Script executed:
# Check test coverage for request option shapes
rg -A5 "http\.request\(|https\.request\(" test/http-proxy-fix-sync.test.ts | grep -E "(host|hostname)" | head -30Repository: NVIDIA/NemoClaw
Length of output: 41
🏁 Script executed:
# Read the canonical http-proxy-fix.js
cat ./nemoclaw-blueprint/scripts/http-proxy-fix.jsRepository: NVIDIA/NemoClaw
Length of output: 3556
🏁 Script executed:
# Search entire test file for any request/host option patterns
rg -n "options\s*=" test/http-proxy-fix-sync.test.ts -A2 -B1 | head -60Repository: NVIDIA/NemoClaw
Length of output: 41
🏁 Script executed:
# Check if there are any http.request() calls with 'host' field in tests
rg "host:" test/ --type js --type ts | grep -v hostname | head -20Repository: NVIDIA/NemoClaw
Length of output: 1977
🏁 Script executed:
# Search for test cases that specifically test http.request with host-only options
rg -n "host-only|host\s*:" test/http-proxy-fix-sync.test.ts -B3 -A3 | head -50Repository: NVIDIA/NemoClaw
Length of output: 41
🏁 Script executed:
# Look at the entire http-proxy-fix test to understand coverage
wc -l test/http-proxy-fix-sync.test.ts && echo "---" && head -100 test/http-proxy-fix-sync.test.tsRepository: NVIDIA/NemoClaw
Length of output: 3824
🏁 Script executed:
# Check if there are any integration or functional tests that might exercise host-only paths
fd -e "test.ts" -e "test.js" | xargs rg -l "http\.request|https\.request" | head -10Repository: NVIDIA/NemoClaw
Length of output: 116
Handle host-only request options in the proxy-fix matcher.
The matcher only checks options.hostname, but http.request() also accepts host. Requests with only { host, port, path } will skip the rewrite and hit the original HTTPS FORWARD rejection.
Suggested fix
- if (
- options.hostname === proxyHost &&
- options.path &&
- options.path.startsWith('https://')
- ) {
+ var requestHost = options.hostname;
+ if (!requestHost && typeof options.host === 'string') {
+ try {
+ requestHost = new URL('http://' + options.host).hostname;
+ } catch (e) {
+ requestHost = options.host;
+ }
+ }
+ if (
+ requestHost === proxyHost &&
+ typeof options.path === 'string' &&
+ options.path.startsWith('https://')
+ ) {This edit must land in both nemoclaw-blueprint/scripts/http-proxy-fix.js and the embedded heredoc in scripts/nemoclaw-start.sh (the test at line 34–56 enforces byte-for-byte equality).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ( | |
| options.hostname === proxyHost && | |
| options.path && | |
| options.path.startsWith('https://') | |
| ) { | |
| var requestHost = options.hostname; | |
| if (!requestHost && typeof options.host === 'string') { | |
| try { | |
| requestHost = new URL('http://' + options.host).hostname; | |
| } catch (e) { | |
| requestHost = options.host; | |
| } | |
| } | |
| if ( | |
| requestHost === proxyHost && | |
| typeof options.path === 'string' && | |
| options.path.startsWith('https://') | |
| ) { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/nemoclaw-start.sh` around lines 878 - 882, The proxy matcher
currently only checks options.hostname against proxyHost so requests that pass {
host, port, path } slip through; update the conditional that uses
options.hostname (and the embedded heredoc copy) to also accept options.host
(e.g., check (options.hostname === proxyHost || options.host === proxyHost))
before testing options.path.startsWith('https://') so host-only request options
are rewritten like hostname-based ones; ensure the same change is made in the
corresponding embedded heredoc to keep the two files byte-for-byte identical.
|
Good analysis on why #2110 didn't land correctly — the three failure modes (build context exclusion, can't extend without breaking the build, Module._load missing bundled ESM) are all valid. The http.request() wrapper approach is the right level to fix this. On the CodeRabbit feedback:
|
#2344) ## Summary Signed replay of #2323 by @lcsmontiel — same changes, commits signed to pass the org signature check. - Replaces `axios-proxy-fix.js` with an `http.request()` wrapper that catches the FORWARD-vs-CONNECT mismatch at the lowest common denominator - Adds `try/catch` around `new URL(options.path)` to prevent process crashes on malformed URLs - Preserves caller-supplied options in the rewritten request - Verified end-to-end on a real proxy-enabled sandbox (see #2323 for full validation table) ## Original PR All design rationale, failure analysis, and E2E validation are documented in #2323. Credit to @lcsmontiel for the fix. ## Test plan - [x] `npx vitest run --project cli test/http-proxy-fix-sync.test.ts` — 6/6 pass - [x] `npx vitest run --project cli test/service-env.test.ts` — 41/41 pass - [x] All pre-commit and pre-push hooks pass - [ ] CI Closes #2109. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added HTTP request interception for improved proxy environment variable handling, with enhanced support for Node.js 22. * **Tests** * Added new test suite validating proxy fix consistency and configuration. * Updated proxy environment variable tests to reflect new implementation. * **Chores** * Removed legacy proxy handling implementation. * Updated startup script to use enhanced proxy mechanism. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Lucas Montiel <lcsmontiel@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@lcsmontiel Thank you for this excellent contribution! The root cause analysis was thorough — identifying all three independent failure modes of the original fix was impressive work, and the http.request wrapper approach is the right architectural call. The end-to-end validation on a real sandbox was also very much appreciated. We replayed your commits (with the try/catch addition) into #2344 to pass the org signature check, and it's now merged to main. Full credit to you for the fix. 🙏 |
Summary
axios/follow-redirects/proxy-from-env/ bundled ESM HTTP clients, currently fails inside NemoClaw sandboxes withFORWARD rejected: HTTPS requires CONNECT(issue axios requests fail with ERR_BAD_RESPONSE inside NemoClaw sandbox — double proxy conflict with NODE_USE_ENV_PROXY #2109). The shipped fix (PR fix(proxy): resolve axios + NODE_USE_ENV_PROXY double-proxy conflict #2110) never fires at runtime.axios-proxy-fix.jswith anhttp.request()wrapper embedded inline in the sandbox entrypoint. The wrapper sits below every Node.js HTTP client, catches the FORWARD-vs-CONNECT mismatch at the lowest common denominator, and rewrites the request so Node 22'sNODE_USE_ENV_PROXYhandles the CONNECT tunnel correctly.axioscall + full Teams bot round-trip now succeed. Repro and before/after table below.Why PR #2110's axios-proxy-fix.js doesn't fire
Three independent failure modes, any one of which breaks the preload:
nemoclaw-blueprint/scripts/is excluded fromstageOptimizedSandboxBuildContext()insrc/lib/sandbox-build-context.ts. Onlyblueprint.yamlandpolicies/are staged.axios-proxy-fix.jsnever reaches the Docker build context, so/opt/nemoclaw-blueprint/scripts/axios-proxy-fix.jsdoes not exist at runtime. The-fcheck innemoclaw-start.shsilently returns false;NODE_OPTIONSis never set.scripts/tostageOptimizedSandboxBuildContext()cache-busts theCOPY nemoclaw-blueprint/Dockerfile layer, which re-runsnpm ciinside the k3s Docker-in-Docker build, which hangs. The optimized build context is deliberately minimal.Module._loadcan't reach bundled ESM. Even assuming the file were present, theModule._loadhook interceptsrequire('axios')by name. OpenClaw'sdist/http-Bh-HtMAg.jsinlinesfollow-redirects+proxy-from-envas ESM — norequire()calls to patch. The Bot Connector reply path uses that bundled code.All three reasons are why the original #2109 reproduction still fails on v0.0.22 despite the PR #2110 merge.
The fix
Wrap
http.request()— the lowest common denominator every HTTP client bottoms out at. Detect FORWARD-mode requests (hostname == proxy IPANDpath.startsWith('https://')) and rewrite them ashttps.request()against the real target.NODE_USE_ENV_PROXY'sEnvHttpProxyAgentthen handles the CONNECT tunnel correctly.Works for:
axios(direct require or bundled)follow-redirects(bundled ESM, no require)proxy-from-env(bundled ESM, no require)Delivery: inline heredoc, written at boot
nemoclaw-blueprint/scripts/http-proxy-fix.js— canonical source for review and tests.scripts/nemoclaw-start.shembeds the same JS inline via aHTTP_PROXY_FIX_EOFheredoc. At PID-1 boot,emit_sandbox_sourced_filewrites it to/tmp/nemoclaw-http-proxy-fix.jswithroot:root 444(same trust-boundary helper used for/tmp/nemoclaw-proxy-env.sh). Thenexport NODE_OPTIONS="... --require /tmp/nemoclaw-http-proxy-fix.js".sandbox-build-context.ts, no new COPY inDockerfile, no runtime-deploy + restart dance. The fix is active on the first sandbox boot.test/http-proxy-fix-sync.test.tsenforces byte-for-byte equality between the canonical JS file and the embedded heredoc. Edits to one force edits to the other or CI fails.validate_tmp_permissionsis extended with the new path on both the root and non-root boot paths. The fix JS is a trust-boundary file — tampering would let the sandbox user inject arbitrary code into every Node process viaNODE_OPTIONS.nemoclaw-blueprint/scripts/axios-proxy-fix.jsis removed.test/service-env.test.tsaxios requests fail with ERR_BAD_RESPONSE inside NemoClaw sandbox — double proxy conflict with NODE_USE_ENV_PROXY #2109 regression tests updated to the new variable name (_PROXY_FIX_SCRIPT) and lose the file-existence fixture (the entrypoint now writes the file unconditionally whenNODE_USE_ENV_PROXY=1). The [All platforms] Discord channel fails with 400 — Node.js EnvHttpProxyAgent uses forward proxy instead of CONNECT tunnel #1570 ws-proxy-fix tests are untouched aside from removing the dead_AXIOS_FIX_SCRIPT=...prop in their fixtures.End-to-end validation
Verified 2026-04-23 on EC2 t3.large (ca-central-1), NemoClaw v0.0.22 + fix, OpenShell v0.0.29, Node 22.22.1.
Direct reproduction of #2109 (inside the sandbox)
Full Teams round-trip
OpenShell egress logs during the run:
Zero
FORWARD rejectedentries. All outbound HTTPS goes through CONNECT correctly.Before/after
https.request()(Node core)axiosdefaultFORWARD rejected)axioswithproxy: falsedistaxios (follow-redirects)Related observation — PR #2296 / #1570
While tracing #2110's failure I noticed that
scripts/nemoclaw-start.shwiresws-proxy-fix.js(added by PR #2296, closes #1570) at the same/opt/nemoclaw-blueprint/scripts/ws-proxy-fix.jspath thataxios-proxy-fix.jsused. Because the optimized sandbox build context still does not stagenemoclaw-blueprint/scripts/, that file has the same delivery gap — the-fcheck silently returns false andNODE_OPTIONSis never set. PR #2296's own test plan left the E2E checkbox unchecked:I did not touch ws-proxy-fix in this PR — different issue, different failure mode (the bug is inside
EnvHttpProxyAgent's FORWARD-vs-CONNECT choice forUpgrade: websocketrequests, which the http.request wrapper in this PR cannot safely handle — it would re-enter the same faulty agent logic). Raising this as a heads-up in case maintainers want a follow-up. Happy to open a separate issue or PR if useful.Scope / non-goals
NODE_USE_ENV_PROXY=1).src/lib/sandbox-build-context.ts. The build context stays minimal — the inline heredoc is the explicit tradeoff.Test plan
npx vitest run --project cli test/http-proxy-fix-sync.test.ts— 6/6 pass (byte-for-byte sync guard)npx vitest run --project cli test/service-env.test.ts— 41/41 pass (updated axios requests fail with ERR_BAD_RESPONSE inside NemoClaw sandbox — double proxy conflict with NODE_USE_ENV_PROXY #2109 regression + unchanged [All platforms] Discord channel fails with 400 — Node.js EnvHttpProxyAgent uses forward proxy instead of CONNECT tunnel #1570 ws-fix tests)npx vitest run --project cli test/nemoclaw-start.test.ts— 68/68 passnpx vitest run --project cli test/sandbox-build-context.test.ts— 2/2 passnpm run typecheck:cli— cleanshellcheck scripts/nemoclaw-start.sh— cleanshfmt -d scripts/nemoclaw-start.sh— no diffs on changed regionsaxios.get('https://clawhub.ai')+ full Teams round-trip — see validation section aboveCloses #2109.
Summary by CodeRabbit
New Features
Improvements
Tests
Chores