Skip to content

fix(inference): shim globalThis.fetch to proxy inference.local for cr… - #4916

Closed
Abhi190702 wants to merge 20 commits into
NVIDIA:mainfrom
Abhi190702:fix/4730-fetch-proxy-inference-local
Closed

fix(inference): shim globalThis.fetch to proxy inference.local for cr…#4916
Abhi190702 wants to merge 20 commits into
NVIDIA:mainfrom
Abhi190702:fix/4730-fetch-proxy-inference-local

Conversation

@Abhi190702

@Abhi190702 Abhi190702 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR fixes a bug where cron agentTurn jobs using a local inference provider (Ollama or vLLM, configured to map via the virtual host https://inference.local/v1) are skipped.
At execution time, the OpenClaw cron scheduler runs a "provider preflight" check using Node’s native fetch() (backed by undici in Node 18+). Since fetch() bypasses the legacy http.request network stack, it bypasses the sandbox's existing proxy-rewrite logic, attempts raw DNS resolution for the virtual hostname inference.local, and fails with getaddrinfo EAI_AGAIN. By shimming fetch() for inference.local requests, the preflight check is correctly routed through the OpenShell sandbox proxy.

Related Issue

Fixes #4730

Changes

  • Extended http-proxy-fix.js to Shim globalThis.fetch:
    • Overrides global fetch (only on Node runtimes >= 18).
    • Limits interception strictly to requests targeting the virtual hostname inference.local (all other hosts bypass the wrapper).
    • Converts matching fetch calls into the same HTTP FORWARD-mode structure that is intercepted and handled by the existing, robust http.request proxy rewrite.
    • Translates streaming response bodies back to native WHATWG streams using stream.Readable.toWeb(res) to preserve full streaming compatibility for downstream LLM consumers.
    • Guarded by __nemoclawFetchPatched to remain completely idempotent when loaded multiple times.
  • Fixed Case-Insensitive Environment collisions in Test Suites:
    • Removed duplicate stubs setting both HTTPS_PROXY and https_proxy in test/http-proxy-fix-e2e.test.ts and test/http-proxy-fix-rewrite.test.ts to prevent test failures on Windows hosts where the environment block is case-insensitive.
  • Improved Platform Portability for Sync Tests:
    • Added conditional skip blocks in test/http-proxy-fix-sync.test.ts if a usable bash shell is not available locally, preventing false failures on Windows machines while ensuring the tests still fail and block regressions in Linux-based CI environments.
  • Added Comprehensive Regression Tests:
    • Introduced test/http-proxy-fix-fetch.test.ts with coverage for fetch rewrite, passthrough, idempotency, and Node < 18 fallback logic.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Verification

  • npx prek run --all-files passes (verified locally, excluding missing system-level hadolint tool on Windows environment)
  • npm test passes (verified all http-proxy-fix suites pass cleanly on Windows)
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: ABHIJEET RANJAN abhijeet.r1907@gmail.com

Summary by CodeRabbit

  • Bug Fixes

    • Ensure HTTPS requests via env proxy use CONNECT-style routing and strip hop-by-hop headers; fetch to the local inference endpoint is routed through the same proxy and patching is idempotent.
  • Tests

    • Added regression tests for fetch/proxy routing and idempotent patching; updated proxy test setups and added a bash-availability guard for one sync test.
  • Documentation

    • Expanded in-script comments explaining proxy/fetch routing behavior.

@copy-pr-bot

copy-pr-bot Bot commented Jun 7, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR extends the http-proxy-fix preload to intercept fetch() calls to https://inference.local/* and route them through the existing FORWARD-mode http.request rewrite, adds proxy parsing and an idempotent fetch patch, updates start-script docs, aligns test environment vars, adds fetch regression tests, and makes the sync test conditional on Bash availability.

Changes

Proxy Fetch Shim and Alignment

Layer / File(s) Summary
Proxy fix fetch shim implementation
nemoclaw-blueprint/scripts/http-proxy-fix.js, scripts/nemoclaw-start.sh
Updates header comments and proxy parsing; adds fetch detection/rewriting helpers that buffer bodies, copy headers, set content-length when needed, forward rewritten FORWARD-mode requests via http.request() -> https.request() targeting the real host, wrap responses into Response objects, and install an idempotent globalThis.fetch patch during init. Shell script docs expanded to describe the rewrite and fetch routing.
Test environment proxy variable alignment
test/http-proxy-fix-e2e.test.ts, test/http-proxy-fix-rewrite.test.ts
Adjusts beforeEach setup to set NODE_USE_ENV_PROXY=1 and HTTPS_PROXY to the configured proxy; removes prior clearing of lowercase/alternate proxy env vars.
Fetch routing regression test suite
test/http-proxy-fix-fetch.test.ts
New Vitest module that stubs/validates fetch routing: ensures fetch('https://inference.local/...') is rewritten through the proxy with correct host/port/path/method/headers/body; verifies non-inference.local requests bypass interception; checks idempotency and behavior when native fetch is absent.

Test Infrastructure Robustness

Layer / File(s) Summary
Bash availability check for sync test
test/http-proxy-fix-sync.test.ts
Adds tryUsableBash() probe using bash -lc, derives bashAvailable, throws in CI if missing, otherwise warns and skips locally; wraps existing preload sync test with it.skipIf(!bashAvailable) and updates Vitest import style.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant FetchShim as Fetch Shim
  participant HttpWrapper as http.request wrapper
  participant Proxy
  participant Destination as Real Host
  Client->>FetchShim: fetch('https://inference.local/...')
  FetchShim->>FetchShim: normalize URL, copy headers/body
  FetchShim->>HttpWrapper: http.request(proxyHost:proxyPort, path=fullTargetURL)
  HttpWrapper->>HttpWrapper: rewrite FORWARD-mode -> https.request(destHost, path)
  HttpWrapper->>Proxy: CONNECT / tunneled request
  Proxy->>Destination: tunnel to destination
  Destination-->>Proxy: response stream
  Proxy-->>HttpWrapper: proxied stream
  HttpWrapper-->>FetchShim: IncomingMessage stream
  FetchShim-->>Client: Response (stream/body)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • NVIDIA/NemoClaw#2344: Both PRs implement/extend the same proxy fix by wrapping http.request() and rewriting FORWARD-mode HTTPS proxy requests into https.request() calls.

Suggested labels

area: local-models

Suggested reviewers

  • prekshivyas

"🐰 I hopped through code, rewrote the trail,
Fetch calls to inference now ride the proxy rail.
Headers and bodies I bundle with care,
No DNS skip — the tunnel's laid bare.
A tiny patch, stitched once, light as hare."

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is truncated and incomplete, ending with 'cr…' instead of specifying the complete scope or objective. Complete the title to clearly describe what the fetch shim does (e.g., 'fix(inference): shim globalThis.fetch to proxy inference.local for cron jobs') for clarity.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR implements a fetch shim that routes inference.local requests through the proxy path, directly addressing the issue #4730 requirement to prevent DNS resolution failures for inference.local.
Out of Scope Changes check ✅ Passed All changes are in-scope: fetch shim and proxy routing for inference.local, test setup corrections for Windows compatibility, bash availability checks, and regression tests for the new fetch behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (2)
scripts/nemoclaw-start.sh (1)

2058-2072: Queue sandbox entrypoint E2Es for this change.

Because this script affects sandbox boot/runtime boundaries that unit tests miss, run:
gh workflow run nightly-e2e.yaml --ref <branch> -f jobs=sandbox-survival-e2e,sandbox-operations-e2e,cloud-e2e,openclaw-slack-pairing-e2e

As per coding guidelines, scripts/nemoclaw-start.sh changes should be validated with the listed sandbox E2E suites.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/nemoclaw-start.sh` around lines 2058 - 2072, The change to
scripts/nemoclaw-start.sh must be validated by queuing the specified sandbox E2E
suites before merging: run the GitHub Actions workflow nightly-e2e.yaml against
your branch and pass the jobs input set to
sandbox-survival-e2e,sandbox-operations-e2e,cloud-e2e,openclaw-slack-pairing-e2e
(e.g. using the gh CLI: gh workflow run nightly-e2e.yaml --ref <branch> -f
jobs=sandbox-survival-e2e,sandbox-operations-e2e,cloud-e2e,openclaw-slack-pairing-e2e);
ensure the workflow completes successfully for scripts/nemoclaw-start.sh changes
and block merge until those E2E suites pass.

Source: Coding guidelines

nemoclaw-blueprint/scripts/http-proxy-fix.js (1)

23-27: Run the proxy-chain E2Es for this L7 rewrite before merge.

This path is explicitly marked for manual FORWARD-mode validation; run:
gh workflow run nightly-e2e.yaml --ref <branch> -f jobs=cloud-e2e,inference-routing-e2e

As per coding guidelines, nemoclaw-blueprint/scripts/http-proxy-fix.js changes require manual validation using cloud-e2e and inference-routing-e2e.

Also applies to: 246-260, 329-329

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nemoclaw-blueprint/scripts/http-proxy-fix.js` around lines 23 - 27, This
change in nemoclaw-blueprint/scripts/http-proxy-fix.js touches a manual
FORWARD-mode L7 rewrite that must be validated by running the cloud and
inference routing end-to-end suites before merging: run the GitHub workflow
nightly-e2e.yaml with ref set to your branch and pass -f
jobs=cloud-e2e,inference-routing-e2e (gh workflow run nightly-e2e.yaml --ref
<branch> -f jobs=cloud-e2e,inference-routing-e2e), exercise the
https://inference.local/* fetch wrapper and confirm FORWARD-mode behavior, and
repeat validation for the other affected regions noted (around the other changes
at the same file ranges) before approving the PR.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@nemoclaw-blueprint/scripts/http-proxy-fix.js`:
- Around line 23-27: This change in nemoclaw-blueprint/scripts/http-proxy-fix.js
touches a manual FORWARD-mode L7 rewrite that must be validated by running the
cloud and inference routing end-to-end suites before merging: run the GitHub
workflow nightly-e2e.yaml with ref set to your branch and pass -f
jobs=cloud-e2e,inference-routing-e2e (gh workflow run nightly-e2e.yaml --ref
<branch> -f jobs=cloud-e2e,inference-routing-e2e), exercise the
https://inference.local/* fetch wrapper and confirm FORWARD-mode behavior, and
repeat validation for the other affected regions noted (around the other changes
at the same file ranges) before approving the PR.

In `@scripts/nemoclaw-start.sh`:
- Around line 2058-2072: The change to scripts/nemoclaw-start.sh must be
validated by queuing the specified sandbox E2E suites before merging: run the
GitHub Actions workflow nightly-e2e.yaml against your branch and pass the jobs
input set to
sandbox-survival-e2e,sandbox-operations-e2e,cloud-e2e,openclaw-slack-pairing-e2e
(e.g. using the gh CLI: gh workflow run nightly-e2e.yaml --ref <branch> -f
jobs=sandbox-survival-e2e,sandbox-operations-e2e,cloud-e2e,openclaw-slack-pairing-e2e);
ensure the workflow completes successfully for scripts/nemoclaw-start.sh changes
and block merge until those E2E suites pass.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8bb8c76b-ef65-451b-8a27-18f945b49a7a

📥 Commits

Reviewing files that changed from the base of the PR and between e2edaad and c9a1fa3.

📒 Files selected for processing (6)
  • nemoclaw-blueprint/scripts/http-proxy-fix.js
  • scripts/nemoclaw-start.sh
  • test/http-proxy-fix-e2e.test.ts
  • test/http-proxy-fix-fetch.test.ts
  • test/http-proxy-fix-rewrite.test.ts
  • test/http-proxy-fix-sync.test.ts
💤 Files with no reviewable changes (2)
  • test/http-proxy-fix-rewrite.test.ts
  • test/http-proxy-fix-e2e.test.ts

@cv cv added the v0.0.61 label Jun 7, 2026
@wscurran wscurran added area: inference Inference routing, serving, model selection, or outputs bug-fix PR fixes a bug or regression provider: ollama Ollama local model provider behavior provider: vllm vLLM local or hosted provider behavior labels Jun 8, 2026
@wscurran

wscurran commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

✨ Thanks for submitting this detailed PR about shimming globalThis.fetch to proxy inference.local for cron agentTurn jobs using a local inference provider. This proposes a way to fix the regression in the inference area.


Related open issues:

@Abhi190702

Copy link
Copy Markdown
Contributor Author

Thanks for the review.

On the two nitpicks from CodeRabbit — I don't have access to
trigger the nightly-e2e.yaml workflow directly from a fork PR,
so I can't run sandbox-survival-e2e, sandbox-operations-e2e,
cloud-e2e or inference-routing-e2e myself. If a maintainer is
able to queue those E2E suites against this branch, I'm happy
to wait for them to pass before merge.

Let me know if there's
anything else I can address from my side.

@cv cv self-assigned this Jun 8, 2026
@cv cv added v0.0.62 and removed v0.0.61 labels Jun 8, 2026
@jyaunches jyaunches added v0.0.64 and removed v0.0.63 labels Jun 11, 2026
@Abhi190702
Abhi190702 force-pushed the fix/4730-fetch-proxy-inference-local branch 2 times, most recently from cc75ff1 to 6938c37 Compare June 11, 2026 12:26
…on preflight

Cron agentTurn jobs using a local Ollama provider were being skipped
because the cron scheduler's provider preflight uses fetch() (undici)
rather than http.request(), bypassing the OpenShell sandbox proxy that
routes the virtual hostname inference.local to the local Ollama instance.

The existing http-proxy-fix.js already patches http.request and
https.request to honour proxy env variables. The preflight never goes
through that path — it calls fetch() directly, which resolves
inference.local via raw DNS, gets EAI_AGAIN, and marks the provider
unreachable, causing the cron job to be skipped entirely.

Extended http-proxy-fix.js to wrap globalThis.fetch for requests whose
hostname is inference.local, routing them through the existing
proxy-aware path. Every other hostname passes through the original
fetch unchanged.

The shim is:
- Idempotent via __nemoclawFetchPatched guard
- A no-op when typeof globalThis.fetch !== 'function' (Node < 18)
- Scoped strictly to inference.local via URL hostname parsing
- Non-destructive to the existing http.request/https.request patch
- Free of hardcoded replacement addresses
- Does not modify NO_PROXY or the managed inference.local routing design

Also fixed in this commit:
- Pre-existing Windows env case collision (HTTPS_PROXY vs https_proxy)
  in the existing proxy test suite causing false failures on Windows
- Bash-dependent sync tests now skip locally when Bash/WSL is
  unavailable, but fail in CI to catch real regressions

Verification:
- npm test (proxy-focused suites) passed
- npm run typecheck passed
- node --check http-proxy-fix.js passed
- git diff --check passed

Fixes NVIDIA#4730

# Conflicts:
#	test/http-proxy-fix-sync.test.ts
@Abhi190702
Abhi190702 force-pushed the fix/4730-fetch-proxy-inference-local branch from 6938c37 to 564b9a3 Compare June 11, 2026 12:29

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
nemoclaw-blueprint/scripts/http-proxy-fix.js (1)

169-178: 💤 Low value

Fallback at line 177 could be more explicit.

The fallback return res; would fail when passed to the Response constructor (which expects a Web stream), but this edge case is unlikely in Node 18+ where Readable.toWeb is available. Consider throwing an error or adding a comment explaining this is unreachable in the target environment.

💡 Optional: Make the fallback more explicit
   var stream = require('stream');
   if (stream.Readable && typeof stream.Readable.toWeb === 'function') {
     return stream.Readable.toWeb(res);
   }
-  return res;
+  // Unreachable in Node 18+ (target environment); Response constructor requires Web stream
+  throw new Error('stream.Readable.toWeb unavailable; Node 18+ required');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nemoclaw-blueprint/scripts/http-proxy-fix.js` around lines 169 - 178, In
responseBody, the fallback "return res" can produce an invalid value for the
Response constructor; update the function (responseBody) to make this case
explicit by either throwing a clear Error (e.g., "Readable.toWeb not available:
cannot convert response stream") or by adding a comment asserting the branch is
unreachable in our Node >=18 target and adding a defensive throw to fail fast;
change the final fallback in responseBody to one of those options so callers
never receive an unsupported value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@nemoclaw-blueprint/scripts/http-proxy-fix.js`:
- Around line 169-178: In responseBody, the fallback "return res" can produce an
invalid value for the Response constructor; update the function (responseBody)
to make this case explicit by either throwing a clear Error (e.g.,
"Readable.toWeb not available: cannot convert response stream") or by adding a
comment asserting the branch is unreachable in our Node >=18 target and adding a
defensive throw to fail fast; change the final fallback in responseBody to one
of those options so callers never receive an unsupported value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 27fc137d-6f15-4e9b-a8b9-9d495f322f41

📥 Commits

Reviewing files that changed from the base of the PR and between 6938c37 and 564b9a3.

📒 Files selected for processing (6)
  • nemoclaw-blueprint/scripts/http-proxy-fix.js
  • scripts/nemoclaw-start.sh
  • test/http-proxy-fix-e2e.test.ts
  • test/http-proxy-fix-fetch.test.ts
  • test/http-proxy-fix-rewrite.test.ts
  • test/http-proxy-fix-sync.test.ts
💤 Files with no reviewable changes (2)
  • test/http-proxy-fix-rewrite.test.ts
  • test/http-proxy-fix-e2e.test.ts
✅ Files skipped from review due to trivial changes (1)
  • scripts/nemoclaw-start.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/http-proxy-fix-sync.test.ts
  • test/http-proxy-fix-fetch.test.ts

@cv cv added v0.0.65 and removed v0.0.64 labels Jun 12, 2026
@cv cv added v0.0.66 and removed v0.0.65 labels Jun 15, 2026
@cv cv added v0.0.67 and removed v0.0.66 labels Jun 23, 2026
@jyaunches jyaunches added v0.0.68 and removed v0.0.67 labels Jun 24, 2026
@cv

cv commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Manual PR Review Advisor result

This PR Review Advisor analysis was run manually via workflow_dispatch, so the workflow did not post its usual sticky comment. Posting the advisor summary here because it produced actionable findings.

Run: https://github.com/NVIDIA/NemoClaw/actions/runs/28206392670


PR Review Advisor

The scoped inference.local fetch shim is directionally appropriate, but the security-sensitive preload needs bounded body handling, harder idempotence, and stronger regression proof for the actual cron-preflight workaround.

Required before merge

  • None.

Resolve or justify before merge

  • Source-of-truth review needed: nemoclaw-blueprint/scripts/http-proxy-fix.js fetch monkeypatch for NemoClaw#4730: The advisor marked localized patch analysis as needs_followup.
    • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Verification hint: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
    • Missing regression test: Direct unit tests cover inference.local fetch routing through the preload, but no changed test invokes the actual cron/provider-preflight call path.
    • Evidence: The new comment block says native fetch bypasses the FORWARD-mode rewrite and can attempt raw DNS, while test/http-proxy-fix-fetch.test.ts directly calls global fetch rather than cron/provider preflight.
  • Bound or avoid full request-body buffering in the fetch shim (nemoclaw-blueprint/scripts/http-proxy-fix.js:197): fetchViaForwardProxy converts every non-GET/HEAD inference.local request body with Buffer.from(await request.clone().arrayBuffer()). That changes native fetch upload semantics from streaming/backpressured to full in-memory buffering and has no maximum size or fallback path.
    • Impact: Any code running in the sandbox/gateway process that can call fetch("https://inference.local/...", { body }) can force this long-lived preload path to materialize an arbitrary-size body in memory. That creates a denial-of-service risk and can also break valid streaming/duplex fetch bodies that native undici would otherwise handle.
    • Recommendation: Add an explicit bounded policy before buffering: either reject inference.local fetch bodies above a small documented limit appropriate for provider preflight, or pass through/fail closed for streaming/unknown-size bodies instead of buffering them unbounded. Catch body materialization errors and return a predictable rejection or fallback consistent with that policy.
    • Verification hint: Read nemoclaw-blueprint/scripts/http-proxy-fix.js around request.clone().arrayBuffer() and confirm there is no size check, stream-type check, or catch around the body materialization before the proxied request is created.
    • Missing regression test: Add a test named like rejects or falls back for oversized inference.local fetch bodies without unbounded buffering that constructs a large or streaming POST body and asserts the shim follows the documented bounded behavior without calling https.request with a fully materialized payload.
    • Evidence: The new code assigns body = Buffer.from(await request.clone().arrayBuffer()) for every method except GET and HEAD, then writes the resulting buffer to the proxied request.
  • Do not let a mutable global flag silently disable the fetch patch (nemoclaw-blueprint/scripts/http-proxy-fix.js:250): wrapFetchForInferenceLocal skips installation whenever globalThis.__nemoclawFetchPatched is truthy, without verifying that globalThis.fetch is actually this wrapper. A colliding preload or early user code can pre-set that property and leave inference.local fetches on the original native DNS path.
    • Impact: The workaround can be disabled silently in the same global namespace it is trying to protect, reintroducing the cron preflight failure and creating a preload tampering/collision risk in a security-sensitive sandbox bootstrap path.
    • Recommendation: Make idempotence verify the actual installed wrapper instead of trusting a public boolean. For example, check globalThis.fetch.__nemoclawInferenceLocalProxyFix === true, use a less collision-prone symbol marker, or patch when the marker is set but fetch is not the NemoClaw wrapper.
    • Verification hint: Read wrapFetchForInferenceLocal and confirm the early return is controlled by globalThis.__nemoclawFetchPatched before checking globalThis.fetch.__nemoclawInferenceLocalProxyFix.
    • Missing regression test: Add a test named like patches inference.local fetch when a stale __nemoclawFetchPatched flag exists but fetch is unwrapped that pre-sets the flag, loads the preload, and verifies inference.local fetch is still routed through the proxy path.
    • Evidence: The function sets and checks globalThis.__nemoclawFetchPatched, while the wrapper marker wrappedFetch.__nemoclawInferenceLocalProxyFix = true is only assigned after the early return gate.
  • Prove the actual cron-provider preflight source boundary or document the workaround removal condition (nemoclaw-blueprint/scripts/http-proxy-fix.js:234): The code comments identify the invalid state as OpenClaw cron provider preflight using native fetch for https://inference.local, and the unit test proves this preload can route an inference.local fetch. The PR still does not pin the actual cron/provider-preflight call path in a regression test, and the code comments do not state when the workaround can be removed.
    • Impact: The linked issue is specifically a skipped cron agentTurn run. A lower-level fetch unit test can pass even if the real OpenClaw preflight path changes transport, runs before this preload is installed, or stops needing the shim. Without a removal condition, this monkeypatch can become permanent transport debt in a sandbox security boundary.
    • Recommendation: Add or identify a focused regression around the provider-preflight call path that runs under the same preload setup and shows https://inference.local/v1 avoids native DNS resolution. Also update the source-boundary comment to state why the source cannot be fixed in this PR and when to remove the shim, such as after bumping to an OpenClaw version whose cron preflight uses the sandbox proxy-aware route.
    • Verification hint: Inspect changed tests: test/http-proxy-fix-fetch.test.ts calls fetch("https://inference.local/v1/models") directly, while no changed test invokes the cron/provider-preflight code path described in the issue. Inspect the comment block around NemoClaw#4730 and note that it does not name a removal condition.
    • Missing regression test: Add a test named like cron provider preflight using https://inference.local/v1 reaches the preload proxy path instead of native DNS resolution, using the smallest available OpenClaw/provider-preflight entry point or a local helper that exercises the same transport boundary.
    • Evidence: The patch adds direct fetch-shim tests but no cron agentTurn or provider-preflight regression, while the workaround comment says native fetch bypasses the existing rewrite and can attempt raw DNS.
  • Add a fetch-path regression for proxy-hop header stripping (test/http-proxy-fix-fetch.test.ts:142): Existing rewrite tests cover hop-by-hop and proxy credential stripping for direct http.request FORWARD-mode calls, but the new fetch-specific path has no test proving that fetch-supplied Host, Proxy-Authorization, Connection, or connection-token headers are stripped before the final target request.
    • Impact: The production code appears to route through the existing sanitizer, but this is a credential-leakage boundary. A future change to the fetch bridge could accidentally bypass the sanitizer and leak proxy-hop credentials or a proxy Host header to inference.local without a fetch-path test catching it.
    • Recommendation: Extend test/http-proxy-fix-fetch.test.ts with a case that sends an inference.local fetch containing proxy-hop headers and asserts the captured final https.request options do not include host, proxy-authorization, connection, or headers named by the Connection token list, while preserving target-intent headers like Authorization.
    • Verification hint: Compare test/http-proxy-fix-rewrite.test.ts header-stripping cases with test/http-proxy-fix-fetch.test.ts; the fetch tests currently assert Authorization and Content-Type preservation but do not assert proxy-hop header removal.
    • Missing regression test: Add a test named like fetch route strips Host Proxy-Authorization and Connection-listed headers before final https request in test/http-proxy-fix-fetch.test.ts.
    • Evidence: The new positive fetch test checks authorization and content-type on captured options, but it does not include or assert removal of proxy-hop headers.

In-scope improvements

  • None.

Test follow-ups to resolve or justify

  • Runtime validation — cron provider preflight using https://inference.local/v1 reaches the preload proxy path instead of native DNS resolution. The changed source is a sandbox preload and startup transport path. Unit coverage is focused and useful, but the bug report concerns runtime cron provider preflight in a managed inference route, so at least one behavior-level validation should prove the actual source boundary.
  • Runtime validation — fetch to https://inference.local/v1/models with proxy-hop headers does not leak Host, Proxy-Authorization, Connection, or Connection-listed headers to the final https.request. The changed source is a sandbox preload and startup transport path. Unit coverage is focused and useful, but the bug report concerns runtime cron provider preflight in a managed inference route, so at least one behavior-level validation should prove the actual source boundary.
  • Runtime validation — oversized or streaming request body to https://inference.local/* is rejected or falls back according to an explicit bounded policy without unbounded buffering. The changed source is a sandbox preload and startup transport path. Unit coverage is focused and useful, but the bug report concerns runtime cron provider preflight in a managed inference route, so at least one behavior-level validation should prove the actual source boundary.
  • Runtime validation — pre-set non-wrapper globalThis.__nemoclawFetchPatched does not silently disable the inference.local fetch shim. The changed source is a sandbox preload and startup transport path. Unit coverage is focused and useful, but the bug report concerns runtime cron provider preflight in a managed inference route, so at least one behavior-level validation should prove the actual source boundary.
  • Runtime validation — inference.local fetch with an explicit port and query preserves the target port, path, and query through the rewrite. The changed source is a sandbox preload and startup transport path. Unit coverage is focused and useful, but the bug report concerns runtime cron provider preflight in a managed inference route, so at least one behavior-level validation should prove the actual source boundary.
  • Add a fetch-path regression for proxy-hop header stripping — Extend test/http-proxy-fix-fetch.test.ts with a case that sends an inference.local fetch containing proxy-hop headers and asserts the captured final https.request options do not include host, proxy-authorization, connection, or headers named by the Connection token list, while preserving target-intent headers like Authorization.
  • Acceptance clause: Issue title: “(v0.0.57 cron job failure) Agent cron job uses inference/gemma4:26b but the local provider endpoint is not reachable at https://inference.local/v1.” — add test evidence or identify existing coverage. http-proxy-fix.js now intercepts https://inference.local/* fetches and test/http-proxy-fix-fetch.test.ts covers https://inference.local/v1/models, but no changed test exercises the actual cron agentTurn provider-preflight path.
  • Acceptance clause: “However, job was skipped with the following message: Agent cron job uses inference/gemma4:26b but the local provider endpoint is not reachable at https://inference.local/v1. Skipping this cron run; OpenClaw will retry the provider preflight on a later scheduled run. Last error: Error: getaddrinfo EAI_AGAIN inference.local” — add test evidence or identify existing coverage. The shim avoids native fetch for inference.local in unit coverage, which addresses the DNS-failure mechanism, but there is no regression that observes the skip path no longer occurs for a cron run.

What looks good

  • The fetch interception is narrowly scoped to https://inference.local/*; unrelated fetches fall back to the original fetch.
  • The patch reuses the existing http.request rewrite path, so established proxy-hop header sanitization and TLS option handling remain centralized.
  • The new Vitest coverage is focused and local, without adding a new runner, fixture framework, workflow validator, or broad E2E abstraction.
  • scripts/nemoclaw-start.sh changes are comment-only around the proxy preload block, reducing executable startup drift risk despite the file being active.

Signed-off-by: ABHIJEET RANJAN <abhijeet.r1907@gmail.com>
@Abhi190702

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed advisor review. I pushed a follow-up hardening pass for the inference.local fetch shim and kept the changes scoped to the review findings.

Latest follow-up commit:

c62723b5ea7f3f19f843de89d4cce4ebc71b4670
fix(inference): harden inference.local fetch shim

What this follow-up is trying to solve

The original fix added a narrow fetch() shim for https://inference.local/* so OpenClaw cron/provider preflight does not bypass the existing sandbox proxy rewrite path and attempt raw DNS resolution for the sandbox-only inference.local hostname.

The advisor review called out a few important hardening points around this preload path:

  • request-body buffering needed to be bounded,
  • fetch patch idempotence needed to avoid trusting a mutable public global flag,
  • the localized monkeypatch needed clearer source-boundary and removal-condition documentation,
  • the new fetch path needed stronger regression coverage for security-sensitive behavior,
  • the cron/provider-preflight behavior needed either direct test evidence or a clear source-boundary justification.

This follow-up addresses those points without expanding the PR into unrelated CLI tests, package changes, workflow changes, source-shape budget changes, or a larger E2E framework.

Files changed in this follow-up

The final hardening diff is limited to:

nemoclaw-blueprint/scripts/http-proxy-fix.js
test/http-proxy-fix-fetch.test.ts

No package files, workflow files, source-shape budget files, or unrelated CLI tests were changed.


1. Bounded request-body handling

Previously, non-GET/HEAD inference.local fetch bodies were materialized through request.clone().arrayBuffer() before being routed into the proxy rewrite path.

That created two risks:

  1. A caller could force the long-lived preload path to buffer an arbitrarily large request body in memory.
  2. Streaming/duplex-style bodies could behave differently from native fetch because the shim had to materialize them before forwarding.

I added an explicit bounded policy for https://inference.local/* fetch bodies.

New body policy

The shim now uses a documented 1 MiB maximum body size for inference.local fetch bridging.

The new behavior is:

  • GET and HEAD requests never buffer a body.
  • Requests with request.body === null fast-return without cloning or buffering.
  • If Content-Length is present, it is validated before buffering.
  • Invalid Content-Length values are rejected.
  • Known oversized bodies are rejected before the proxied request is created.
  • Materialized bodies are checked again after arrayBuffer().
  • Bodies that exceed the limit after materialization are rejected.
  • Body materialization errors are caught and rejected predictably.
  • Rejected bodies do not create http.request / https.request.
  • If a safe body exists and no Content-Length was provided, the shim sets an accurate content-length.

Content-Length validation

The Content-Length check is intentionally strict. It rejects values that are:

  • non-finite,
  • negative,
  • non-integer,
  • non-digit,
  • malformed,
  • or larger than the configured 1 MiB limit.

This is meant to keep the preload from becoming an unbounded buffering path while still supporting the small JSON-style provider/preflight requests this route is meant for.


2. Hardened fetch patch idempotence

The previous idempotence check relied on this public mutable flag:

globalThis.__nemoclawFetchPatched

The advisor pointed out that a stale or colliding value there could silently prevent the shim from installing, leaving https://inference.local/* fetches on the native DNS path.

I changed the idempotence check so the shim verifies the actual installed fetch function instead.

The wrapper is marked with:

__nemoclawInferenceLocalProxyFix === true

The preload now checks whether globalThis.fetch itself has the wrapper marker before deciding it is already patched.

Result

This means:

  • A stale globalThis.__nemoclawFetchPatched = true no longer disables the shim.
  • If fetch is unwrapped, the shim still patches it.
  • If fetch is already the NemoClaw wrapper, the preload remains idempotent.
  • The compatibility flag globalThis.__nemoclawFetchPatched is still set after successful patching for any external code that may inspect it.

3. Source-boundary and removal-condition documentation

I expanded the NemoClaw#4730 comment block in http-proxy-fix.js so the localized workaround has a clear boundary and exit condition.

The comment now documents:

Invalid state

OpenClaw cron/provider preflight can call native fetch() for:

https://inference.local/v1

That can trigger a raw DNS lookup and fail with:

getaddrinfo EAI_AGAIN inference.local

because inference.local is a sandbox-only virtual hostname that needs to stay inside the OpenShell proxy route.

Source boundary

This file is the sandbox preload transport boundary. It is loaded at sandbox boot through the preload mechanism and is the controlled layer where this repo can keep inference.local requests inside the existing proxy rewrite path.

Source-fix constraint

The cron/provider preflight path is version-coupled / external to this localized preload fix. I did not try to rewrite broader cron/provider logic in this PR because that would expand the scope significantly beyond the current transport bug.

Regression proof

The regression tests prove the transport boundary behavior controlled by this preload:

https://inference.local/* fetch
→ does not go through raw native DNS path
→ enters the existing FORWARD-mode proxy rewrite path

Removal condition

The comment now states that this shim should be removed once OpenClaw cron/provider preflight uses the sandbox proxy-aware provider route directly, or once an upgraded OpenClaw version no longer uses raw native fetch for inference.local.


4. Regression coverage added

I expanded test/http-proxy-fix-fetch.test.ts with focused coverage for the advisor findings.

The fetch test suite now covers:

Routing / scope

  • https://inference.local/* fetches route through the existing FORWARD-mode rewrite path.
  • Non-inference.local fetches pass through to the original fetch unchanged.
  • The shim remains scoped to https://inference.local/*.

Idempotence

  • Loading the preload multiple times is idempotent.
  • A stale __nemoclawFetchPatched flag does not prevent patching when fetch is not actually wrapped.
  • globalThis.fetch being undefined remains a no-op.

Body safety

  • Oversized inference.local request bodies reject without creating a proxied request.
  • Invalid Content-Length rejects without creating a proxied request.
  • Unsafe body materialization is handled predictably by the bounded helper.

Header boundary

The fetch path now has regression coverage for proxy-hop header stripping.

The test verifies that proxy-hop headers are not leaked to the final target request, including:

Host
Proxy-Authorization
Connection
Connection-listed token headers

It also verifies that target-intent headers are preserved, including:

Authorization
Content-Type

URL preservation

The fetch rewrite now has explicit coverage for preserving:

protocol
hostname
explicit port
path
query string

for a URL like:

https://inference.local:8443/v1/models?foo=bar

5. Cron/provider-preflight regression note

I looked for a small stable cron/provider-preflight entry point that could be invoked directly in this repo without pulling in a much broader E2E surface.

I could not find one that looked appropriate for a focused regression in this PR.

Because of that, I intentionally kept the proof at the boundary this preload controls:

native fetch to https://inference.local/*
→ preload shim
→ existing proxy rewrite path

This proves the transport behavior needed for the provider-preflight workaround while avoiding a large new cron/E2E harness in this patch.

The source-boundary comment now also documents why this localized behavior exists and when it should be removed.


Advisor finding mapping

Advisor concern Status Notes
Bound or avoid full request-body buffering Resolved Added 1 MiB bounded policy, pre-check, post-check, and predictable rejection.
Avoid mutable-global-only idempotence Resolved Checks the actual fetch wrapper marker instead of trusting only __nemoclawFetchPatched.
Source-of-truth / source-boundary review Justified Added invalid-state, source-boundary, source-fix constraint, regression-proof, and removal-condition comment.
Oversized / unsafe body regression Resolved Added oversized body and invalid Content-Length tests.
Stale global flag regression Resolved Added stale __nemoclawFetchPatched test.
Fetch-path proxy-hop header stripping Resolved Added fetch-path header stripping regression.
Cron/provider-preflight direct path proof Justified No small stable entry point found; covered the controlled preload transport boundary instead.
Explicit port/path/query preservation Resolved Added regression coverage.

Local verification

I ran the focused and related checks locally.

npx vitest run test/http-proxy-fix-fetch.test.ts

Result:

9/9 passed

Related proxy tests:

11 passed
2 expected Windows-only skips

Source-shape:

npm run source-shape:check
0 source-shape cases

Diff check:

git diff --check
passed

Hadolint:

hadolint --version
Haskell Dockerfile Linter 2.14.0

Hadolint also passed in the local static run.


Scope intentionally not changed

I intentionally did not change:

  • package files,
  • workflow files,
  • source-shape budget,
  • unrelated CLI tests,
  • flaky timing tests,
  • broader cron/provider implementation,
  • dependency setup,
  • or E2E framework structure.

The follow-up is limited to hardening the inference.local fetch shim and adding regression tests around the reviewed risk areas.

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: cron-preflight-inference-local, inference-routing
Optional E2E: network-policy

Dispatch hint: cron-preflight-inference-local,inference-routing

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: target/main
Head: HEAD
Confidence: high

Required E2E

  • cron-preflight-inference-local: Directly validates the changed production path: a freshly onboarded sandbox invokes OpenClaw's cron model-provider preflight against its managed inference.local provider, proving the fetch path avoids raw DNS and reaches hosted inference.
  • inference-routing: Validates hosted inference onboarding, sandbox routing, failure handling, and assistant-facing inference behavior adjacent to the modified proxy transport preload.

Optional E2E

  • network-policy: Useful adjacent confidence that sandbox egress and managed inference routing remain constrained correctly, although this PR does not modify network-policy assets.

New E2E recommendations

  • None.

Dispatch hint

  • Workflow: .github/workflows/e2e.yaml
  • jobs input: cron-preflight-inference-local,inference-routing

@cv cv added the v0.0.82 label Jul 12, 2026
@cv cv removed the v0.0.82 label Jul 12, 2026
@prekshivyas

Copy link
Copy Markdown
Collaborator

Closing as superseded by #5129, which merged the fix for the same linked issue (#4730). I rechecked this PR's exact head and current main before closing.

There is therefore no remaining linked behavior for this broader global fetch shim to land. Keeping it would add unnecessary long-lived transport semantics, including request-body buffering and global wrapper/idempotence behavior, for a bug already fixed at the narrower source boundary.

Thank you for the investigation and implementation work here. If a different non-cron inference.local fetch path fails in the future, it should be reported with that concrete call site and handled as a separate narrowly scoped fix.

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

Labels

area: inference Inference routing, serving, model selection, or outputs bug-fix PR fixes a bug or regression provider: ollama Ollama local model provider behavior provider: vllm vLLM local or hosted provider behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(v0.0.57 cron job failure) Agent cron job uses inference/gemma4:26b but the local provider endpoint is not reachable at https://inference.local/v1.

5 participants