Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,33 @@ reviews:
gh workflow run nightly-e2e.yaml --ref <branch> -f jobs=cloud-inference-e2e
```

- path: "test/e2e/test-cron-preflight-inference-local-e2e.sh"
instructions: |
This script onboards a sandbox against the managed cloud
inference provider, then loads OpenClaw's cron isolated-agent
preflight runtime directly from the in-sandbox dist and calls
`preflightCronModelProvider` against the onboarded
`inference.local` provider/model, asserting the result reports
`status: "available"` and never `EAI_AGAIN` or the
"local provider endpoint is not reachable" message.

Exercises the Dockerfile fetch-guard patch that opts the cron
model-provider preflight into trusted env-proxy mode so the
managed inference hostname (`inference.local`) routes through
the OpenShell L7 proxy instead of failing pinned DNS lookup.
The scheduler boundary is intentionally bypassed: the cron CLI
surfaces require `operator.admin` scope, which the in-sandbox
auto-pair approval sweep deliberately omits.

**E2E test recommendation:**
- `cron-preflight-inference-local-e2e` — direct cron preflight
runtime probe via OpenShell L7 proxy

To run selectively:
```
gh workflow run nightly-e2e.yaml --ref <branch> -f jobs=cron-preflight-inference-local-e2e
```

- path: "test/e2e/test-openclaw-inference-switch.sh"
instructions: |
This script validates OpenClaw model/provider switching with
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/nightly-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ on:
description: >-
Comma-separated job names to run (empty = all).
Valid: cloud-e2e, cloud-onboard-e2e, cloud-inference-e2e,
cron-preflight-inference-local-e2e,
agent-turn-latency-e2e, skill-agent-e2e, openclaw-skill-cli-e2e,
docs-validation-e2e, messaging-providers-e2e, openclaw-slack-pairing-e2e,
openclaw-tui-chat-correlation-e2e, issue-4434-tui-unreachable-inference-e2e,
Expand Down Expand Up @@ -239,6 +240,21 @@ jobs:
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-cloud-inference"}'
nvidia_api_key: true
secrets: *nightly-e2e-default-secrets
cron-preflight-inference-local-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',cron-preflight-inference-local-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-cron-preflight-inference-local-e2e.sh
timeout_minutes: 30
artifact_name: "install-log-cron-preflight-inference-local"
artifact_path: "/tmp/nemoclaw-e2e-cron-preflight-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-cron-preflight"}'
nvidia_api_key: true
secrets: *nightly-e2e-default-secrets
agent-turn-latency-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
Expand Down Expand Up @@ -2229,6 +2245,7 @@ jobs:
cloud-e2e,
cloud-onboard-e2e,
cloud-inference-e2e,
cron-preflight-inference-local-e2e,
agent-turn-latency-e2e,
skill-agent-e2e,
openclaw-skill-cli-e2e,
Expand Down Expand Up @@ -2346,6 +2363,7 @@ jobs:
cloud-e2e,
cloud-onboard-e2e,
cloud-inference-e2e,
cron-preflight-inference-local-e2e,
agent-turn-latency-e2e,
skill-agent-e2e,
openclaw-skill-cli-e2e,
Expand Down Expand Up @@ -2522,6 +2540,7 @@ jobs:
cloud-e2e,
cloud-onboard-e2e,
cloud-inference-e2e,
cron-preflight-inference-local-e2e,
agent-turn-latency-e2e,
skill-agent-e2e,
openclaw-skill-cli-e2e,
Expand Down
65 changes: 65 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,71 @@ RUN set -eu; \
patch_fail "Patch 4 cannot safely skip"; \
fi; \
fi; \
# --- Patch 6: cron model-provider preflight opts into trusted env-proxy mode --- \
# Reviewed against openclaw@2026.5.27 dist: the cron isolated-agent preflight \
# (`probeLocalProviderEndpoint`) calls `fetchWithSsrFGuard` with \
# `auditContext: "cron-model-provider-preflight"` and a narrow hostname-allowlist \
# SsrFPolicy from `buildLocalProviderSsrFPolicy`, but does not pass a `mode`. \
# Default STRICT mode pins DNS for the managed inference hostname \
# (`inference.local`), which is intentionally only resolvable through the \
# OpenShell L7 proxy — pinned `dns.lookup` therefore fails with EAI_AGAIN and \
# the scheduler permanently skips every cron run. Inject \
# `mode: "trusted_env_proxy"` so the call uses the env proxy dispatcher; SSRF \
# protection is retained through the existing hostname allowlist and the \
# proxy's own ACLs. \
# \
# The patch keys on the co-located shape of the reviewed preflight call: in \
# any file that mentions the audit context literal, both the \
# `fetchWithSsrFGuard(` helper and the `buildLocalProviderSsrFPolicy` policy \
# builder must appear; the audit literal itself must appear exactly once; and \
# after patching exactly one patched literal must remain. Any ambiguous \
# multi-callsite or mixed patched/unpatched layout fails the image build \
# rather than silently widening the rewrite. \
# \
# Removal condition: drop this block (and any related `OC_VERSION` floor bump) \
# once an OpenClaw release sets `mode: "trusted_env_proxy"` directly at the \
# preflight call site or otherwise routes the managed inference base URL \
# through the env-proxy dispatcher by default. The reviewed shape lives at \
# `src/cron/isolated-agent/model-preflight.runtime.ts` in the openclaw repo. \
preflight_files="$(grep -RIlF --include='*.js' 'cron-model-provider-preflight' "$OC_DIST" || true)"; \
if [ -n "$preflight_files" ]; then \
patched_preflight=0; \
for f in $preflight_files; do \
audit_count="$(grep -Fc 'auditContext: "cron-model-provider-preflight"' "$f" || true)"; \
[ "${audit_count:-0}" -ge 1 ] \
|| patch_fail "Patch 6 shape gate: $f mentions cron-model-provider-preflight but has no auditContext literal"; \
[ "${audit_count:-0}" -eq 1 ] \
|| patch_fail "Patch 6 shape gate: $f has ${audit_count} auditContext literals (expected exactly 1); refusing ambiguous multi-callsite rewrite"; \
grep -Fq 'fetchWithSsrFGuard(' "$f" \
|| patch_fail "Patch 6 shape gate: $f has cron-model-provider-preflight but no fetchWithSsrFGuard call"; \
grep -Fq 'buildLocalProviderSsrFPolicy' "$f" \
|| patch_fail "Patch 6 shape gate: $f has cron-model-provider-preflight but no buildLocalProviderSsrFPolicy"; \
patched_count="$(grep -Fc 'mode: "trusted_env_proxy", auditContext: "cron-model-provider-preflight"' "$f" || true)"; \
if [ "${patched_count:-0}" -eq 1 ]; then \
echo "INFO: Patch 6 already present in $f"; \
elif [ "${patched_count:-0}" -eq 0 ]; then \
sed -i -E 's|auditContext: "cron-model-provider-preflight"|mode: "trusted_env_proxy", auditContext: "cron-model-provider-preflight"|g' "$f"; \
new_patched_count="$(grep -Fc 'mode: "trusted_env_proxy", auditContext: "cron-model-provider-preflight"' "$f" || true)"; \
[ "${new_patched_count:-0}" -eq 1 ] \
|| patch_fail "Patch 6 verification: expected exactly one patched literal in $f, found ${new_patched_count}"; \
patched_preflight=1; \
else \
patch_fail "Patch 6 shape gate: $f has ${patched_count} already-patched literals (expected 0 or 1); refusing mixed-state rewrite"; \
fi; \
done; \
if [ "$patched_preflight" = "1" ]; then \
echo "INFO: Patch 6 applied to OpenClaw ${OC_VERSION} cron preflight trusted env-proxy"; \
fi; \
else \
preflight_refs="$(grep -RIlE --include='*.js' 'preflightCronModelProvider|probeLocalProviderEndpoint' "$OC_DIST" || true)"; \
if [ -z "$preflight_refs" ]; then \
echo "INFO: OpenClaw ${OC_VERSION} has no cron model-provider preflight; Patch 6 not needed"; \
else \
echo "ERROR: Patch 6 target missing but cron preflight references remain:" >&2; \
printf '%s\n' "$preflight_refs" | head -n 5 >&2; \
patch_fail "Patch 6 cannot safely skip"; \
fi; \
fi; \
# --- Patch 3: follow symlinks in plugin-install path checks (#2203) --- \
# OpenClaw's install-safe-path and install-package-dir reject symlinked \
# directories via lstat. Changing lstat → stat in these two modules lets \
Expand Down
11 changes: 11 additions & 0 deletions test/e2e-scenario/migration/legacy-inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,17 @@
"deletionReady": false,
"notes": "Initial completeness row; classify detailed coverage and deletion evidence in the owning migration issue before deleting."
},
{
"legacyScript": "test/e2e/test-cron-preflight-inference-local-e2e.sh",
"domain": "inference",
"ownerIssue": "#4349",
"status": "not-migrated",
"targetVitestScenarios": [],
"bridgeProbes": [],
"retiredReason": "",
"deletionReady": false,
"notes": "Covers OpenClaw cron isolated-agent provider preflight against the managed inference hostname; classify detailed coverage and deletion evidence in the owning migration issue before deleting."
},
{
"legacyScript": "test/e2e/test-common-egress-agent-e2e.sh",
"domain": "security-policy",
Expand Down
Loading
Loading