Skip to content

E2E main

E2E main #7

Workflow file for this run

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: E2E
run-name: "${{ inputs.checkout_sha != '' && format('E2E PR #{0} ({1})', inputs.pr_number, inputs.correlation_id) || format('E2E {0}', github.ref_name) }}"
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
inputs:
targets:
description: "Optional comma-separated typed target ids. Empty runs all supported live targets."
required: false
default: ""
type: string
jobs:
description: "Optional comma-separated E2E test IDs. Empty runs default-enabled tests only when targets is also empty; explicit-only tests openshell-gateway-auth-contract, mcp-bridge-dev, hermes-gpu-startup, sandbox-rlimits-connect, and jetson-nvmap-gpu are skipped unless selected."
required: false
default: ""
type: string
inference_mode:
description: "Inference adapter mode for compatible Vitest E2E jobs: mock, internal-nvidia, or public-nvidia."
required: false
default: "mock"
type: choice
options:
- mock
- internal-nvidia
- public-nvidia
allow_jetson_runner_queue:
description: "Repository administrators only: before setting true for jetson-nvmap-gpu, confirm an online Jetson runner in the authoritative NVIDIA/NemoClaw Settings -> Actions -> Runners inventory; queued jobs do not honor timeout-minutes before assignment."
required: false
default: false
type: boolean
pr_number:
description: Optional PR number for selective-dispatch result comments.
required: false
type: string
default: ""
post_to_slack:
description: Post a selective-dispatch scorecard to the preview Slack route.
required: false
default: false
type: boolean
checkout_sha:
description: PR SHA selected by the controller.
required: false
default: ""
type: string
base_sha:
description: PR base commit selected by the controller.
required: false
default: ""
type: string
workflow_sha:
description: Trusted E2E workflow commit selected by the controller.
required: false
default: ""
type: string
plan_hash:
description: SHA-256 of the selected E2E plan.
required: false
default: ""
type: string
correlation_id:
description: Run correlation ID (UUIDv4).
required: false
default: ""
type: string
permissions:
contents: read
pull-requests: read
concurrency:
group: e2e-${{ github.ref }}-${{ inputs.checkout_sha != '' && format('pr-{0}', inputs.pr_number) || inputs.targets || 'supported' }}-${{ inputs.checkout_sha != '' && 'pr-gate' || inputs.jobs || 'all-jobs' }}
cancel-in-progress: ${{ inputs.checkout_sha != '' }}
env:
NEMOCLAW_E2E_EXPECTED_SHA: ${{ inputs.checkout_sha }}
NEMOCLAW_E2E_PLAN_HASH: ${{ inputs.plan_hash }}
NEMOCLAW_E2E_CORRELATION_ID: ${{ inputs.correlation_id }}
NEMOCLAW_E2E_SHARD: default
jobs:
generate-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.controller_matrix.outputs.matrix || steps.matrix.outputs.matrix }}
test_matrix: ${{ steps.matrix.outputs.test_matrix }}
hermes_selected: ${{ steps.matrix.outputs.hermes_selected }}
explicit_only_jobs: ${{ steps.matrix.outputs.explicit_only_jobs }}
steps:
- id: controller_matrix
name: Build trusted controller target matrix
if: ${{ inputs.checkout_sha != '' }}
env:
TARGETS: ${{ inputs.targets }}
shell: bash
run: |
set -euo pipefail
case "${TARGETS}" in
"")
matrix='[]'
;;
ubuntu-repo-cloud-langchain-deepagents-code)
matrix='[{"id":"ubuntu-repo-cloud-langchain-deepagents-code","runner":"ubuntu-latest","label":"ubuntu-repo-cloud-langchain-deepagents-code"}]'
;;
*)
echo "::error::PR E2E target is not approved by the trusted controller" >&2
exit 1
;;
esac
printf 'matrix=%s\n' "${matrix}" >> "${GITHUB_OUTPUT}"
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Validate controller dispatch
if: ${{ inputs.checkout_sha != '' }}
env:
BASE_SHA: ${{ inputs.base_sha }}
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
GITHUB_TOKEN: ${{ github.token }}
JOBS: ${{ inputs.jobs }}
PLAN_HASH: ${{ inputs.plan_hash }}
PR_NUMBER: ${{ inputs.pr_number }}
CORRELATION_ID: ${{ inputs.correlation_id }}
TARGETS: ${{ inputs.targets }}
WORKFLOW_EVENT: ${{ github.event_name }}
WORKFLOW_REF: ${{ github.ref }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: |
set -euo pipefail
[[ "$WORKFLOW_EVENT" == "workflow_dispatch" && "$WORKFLOW_REF" == "refs/heads/main" ]] || { echo "::error::PR E2E runs must be dispatched from main"; exit 1; }
[[ "$CHECKOUT_SHA" =~ ^[a-f0-9]{40}$ ]] || { echo "::error::checkout_sha must be a lowercase 40-character SHA"; exit 1; }
[[ "$BASE_SHA" =~ ^[a-f0-9]{40}$ ]] || { echo "::error::base_sha must be a lowercase 40-character SHA"; exit 1; }
[[ "$EXPECTED_WORKFLOW_SHA" =~ ^[a-f0-9]{40}$ && "$WORKFLOW_SHA" == "$EXPECTED_WORKFLOW_SHA" ]] || { echo "::error::workflow_sha must match the trusted workflow commit"; exit 1; }
[[ "$(git rev-parse --verify HEAD)" == "$CHECKOUT_SHA" ]] || { echo "::error::checked-out commit does not match checkout_sha"; exit 1; }
[[ "$PLAN_HASH" =~ ^[a-f0-9]{64}$ ]] || { echo "::error::plan_hash must be a lowercase SHA-256"; exit 1; }
[[ "$CORRELATION_ID" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$ ]] || { echo "::error::correlation_id must be a lowercase UUIDv4"; exit 1; }
[[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] || { echo "::error::pr_number must be a positive integer"; exit 1; }
[[ -n "$JOBS" || -n "$TARGETS" ]] || { echo "::error::PR E2E runs require controller-selected jobs or targets"; exit 1; }
[[ -z "$TARGETS" || "$TARGETS" == "ubuntu-repo-cloud-langchain-deepagents-code" ]] || { echo "::error::PR E2E target is not approved by the trusted controller"; exit 1; }
pull_json="$(curl --fail --silent --show-error --proto '=https' \
--header "Authorization: Bearer ${GITHUB_TOKEN}" \
--header "Accept: application/vnd.github+json" \
--header "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}")"
[[ "$(jq -r '.state' <<< "$pull_json")" == "open" ]] || { echo "::error::pull request must still be open"; exit 1; }
[[ "$(jq -r '.head.repo.full_name // ""' <<< "$pull_json")" == "$GITHUB_REPOSITORY" ]] || { echo "::error::pull request must originate from this repository"; exit 1; }
[[ "$(jq -r '.head.sha' <<< "$pull_json")" == "$CHECKOUT_SHA" ]] || { echo "::error::checkout_sha must match the PR SHA"; exit 1; }
[[ "$(jq -r '.base.sha' <<< "$pull_json")" == "$BASE_SHA" ]] || { echo "::error::base_sha must match the PR base commit"; exit 1; }
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
with:
build-cli: "false"
- id: matrix
name: Generate E2E target matrix
env:
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
CONTROLLER_MATRIX: ${{ steps.controller_matrix.outputs.matrix }}
INFERENCE_MODE: ${{ inputs.inference_mode || 'mock' }}
JOBS: ${{ inputs.jobs }}
TARGETS: ${{ inputs.targets }}
run: |
set -euo pipefail
npx tsx tools/e2e/workflow-plan.mts --ci-output
if [ -n "${CHECKOUT_SHA}" ]; then
matrix="$(sed -n 's/^matrix=//p' "${GITHUB_OUTPUT}")"
expected_controller_matrix="$(jq -c 'map({id, runner}) | sort_by(.id)' <<< "${CONTROLLER_MATRIX}")"
actual_controller_matrix="$(jq -c 'map({id, runner}) | sort_by(.id)' <<< "${matrix}")"
if [ "${actual_controller_matrix}" != "${expected_controller_matrix}" ]; then
: > "${GITHUB_OUTPUT}"
echo "::error::E2E planner matrix does not match controller-selected targets" >&2
exit 1
fi
fi
live:
needs: generate-matrix
if: ${{ needs.generate-matrix.outputs.matrix != '[]' }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
env:
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
# Keep only the credential-bearing step anchored. Cleanup mappings stay
# explicit because strict YAML decoders reject 100 or more aliases here.
- &dockerhub-auth
name: Authenticate to Docker Hub
uses: NVIDIA/NemoClaw/.github/actions/docker-auth-setup@78091da47e290f49b8fe3f3e70b72362a0853928
with:
auth-required: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && inputs.checkout_sha == '' && '1' || '0' }}
username: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && inputs.checkout_sha == '' && secrets.DOCKERHUB_USERNAME || '' }}
token: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && inputs.checkout_sha == '' && secrets.DOCKERHUB_TOKEN || '' }}
- name: Configure live E2E trace directory
env:
TARGET_ID: ${{ matrix.id }}
shell: bash
run: |
set -euo pipefail
printf 'NEMOCLAW_TRACE_DIR=%s\n' "${RUNNER_TEMP}/nemoclaw-e2e-traces/${TARGET_ID}" >> "${GITHUB_ENV}"
# invalidState: the selected PR-modifiable TUI check needs a PTY driver,
# but the fixed GitHub-hosted runner image does not provide expect.
# sourceBoundary: privileged host setup runs from the first-party
# host-dependency-setup action pinned to an immutable full SHA, never the
# PR-controlled target ref; the check only consumes expect without privilege.
# whyNotSourceFix: GitHub-hosted jobs cannot use a repository-owned host
# image, and caching privileged dpkg state between clean runners is not
# supported.
# regressionTest: the workflow-boundary suite pins this target, condition,
# ordering, action provenance, and package mapping.
# removalCondition: remove the install when the hosted runner supplies
# expect or the acceptance check no longer requires a PTY.
- name: Install Deep Agents Code TUI host dependencies
if: ${{ matrix.id == 'ubuntu-repo-cloud-langchain-deepagents-code' }}
uses: NVIDIA/NemoClaw/.github/actions/host-dependency-setup@4def1501b34ce586f83b91af50a66b5d22b31d75
with:
packages: expect
# Configure NEMOCLAW_TRACE_DIR before workspace prep so every child
# command writes raw traces under runner temp, never under upload roots.
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
# invalidState: a profile plugin installed with --no-deps can import even
# when an incomplete base image omitted its required upstream packages.
# sourceBoundary: this trusted workflow scopes the repo-owned stripped-base
# build to the exact DCode target; the production Dockerfile must reject it
# at the isolated import gate before its later dependency-consistency check.
# whyNotSourceFix: dependency completeness belongs to the hash-locked base;
# resolving dependencies during local plugin install would duplicate that
# trust boundary, so the regression constructs the invalid input instead.
# regressionTest: workflow-boundary tests pin the target, script, and
# ordering; build-gate tests pin the base build and failure contract.
# removalCondition: remove only if package installation no longer uses
# --no-deps or an equivalent earlier build gate proves both imports.
- name: Verify DCode profile import gate rejects missing base dependencies
if: ${{ matrix.id == 'ubuntu-repo-cloud-langchain-deepagents-code' }}
shell: bash
run: bash scripts/check-dcode-profile-import-gate.sh
- name: Run live E2E tests
env:
E2E_TARGET_ID: ${{ matrix.id }}
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
TARGET_ID: ${{ matrix.id }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/registry-targets.test.ts --selector "^${TARGET_ID}$"
# The sanitizer reads raw traces only after checking the workflow-owned
# runner-temp path, then writes the timing-only file into upload roots.
- name: Build trusted live E2E timing summary
if: always()
env:
TARGET_ID: ${{ matrix.id }}
shell: bash
run: |
set -euo pipefail
expected_trace_dir="${RUNNER_TEMP}/nemoclaw-e2e-traces/${TARGET_ID}"
if [ -z "${RUNNER_TEMP}" ] || [ "${NEMOCLAW_TRACE_DIR}" != "${expected_trace_dir}" ]; then
echo "::error::Refusing to sanitize unexpected raw trace path" >&2
exit 1
fi
python3 scripts/e2e/sanitize-trace-timing.py \
"${NEMOCLAW_TRACE_DIR}" \
"${E2E_ARTIFACT_DIR}/${TARGET_ID}"
# Cleanup intentionally runs after sanitization and before upload so raw
# trace JSON never becomes part of the uploaded artifact surface.
- name: Delete raw live E2E traces
if: always()
env:
TARGET_ID: ${{ matrix.id }}
shell: bash
run: |
set -euo pipefail
expected_trace_dir="${RUNNER_TEMP}/nemoclaw-e2e-traces/${TARGET_ID}"
if [ -z "${RUNNER_TEMP}" ] || [ "${NEMOCLAW_TRACE_DIR}" != "${expected_trace_dir}" ]; then
echo "::error::Refusing to delete unexpected raw trace path" >&2
exit 1
fi
rm -rf -- "${NEMOCLAW_TRACE_DIR}"
- name: Summarize artifacts
if: always()
env:
TARGET_ID: ${{ matrix.id }}
TARGET_LABEL: ${{ matrix.label }}
run: |
python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY"
import json
import os
from pathlib import Path
root = Path(os.environ["E2E_ARTIFACT_DIR"]) / os.environ["TARGET_ID"]
plan_path = root / "run-plan.json"
print("## E2E Targets")
print()
print("- Project: `e2e-live`")
print(f"- Target: `{os.environ['TARGET_ID']}`")
print(f"- Label: `{os.environ['TARGET_LABEL']}`")
print(f"- Artifact root: `{root}`")
print()
print("| Target | Manifest | Expected state | Suites | Phases |")
print("| --- | --- | --- | --- | --- |")
if plan_path.exists():
plan = json.loads(plan_path.read_text(encoding="utf-8"))
suites = ", ".join(plan.get("suiteIds") or []) or "(none)"
phases = ", ".join(plan.get("phases") or []) or "(none)"
print(
"| "
f"`{plan.get('targetId') or os.environ['TARGET_ID']}` | "
f"`{plan.get('manifestPath') or 'not-yet-defined'}` | "
f"`{plan.get('expectedStateId') or 'not-yet-defined'}` | "
f"{suites} | {phases} |"
)
else:
print(
"| "
f"`{os.environ['TARGET_ID']}` | `(missing run-plan.json)` | "
"`(missing run-plan.json)` | `(missing)` | `(missing)` |"
)
PY
- name: Upload E2E artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-${{ matrix.id }}
path: |
e2e-artifacts/live/${{ matrix.id }}/run-plan.json
e2e-artifacts/live/${{ matrix.id }}/target.json
e2e-artifacts/live/${{ matrix.id }}/target-result.json
e2e-artifacts/live/${{ matrix.id }}/test-progress.json
e2e-artifacts/live/${{ matrix.id }}/environment.result.json
e2e-artifacts/live/${{ matrix.id }}/onboarding.result.json
e2e-artifacts/live/${{ matrix.id }}/state-validation.result.json
e2e-artifacts/live/${{ matrix.id }}/cloud-onboard-trace-timing-summary.json
e2e-artifacts/live/risk-signal.json
e2e-artifacts/live/${{ matrix.id }}/actions/
e2e-artifacts/live/${{ matrix.id }}/logs/
e2e-artifacts/live/${{ matrix.id }}/shell/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
# Credential-free tests opt in with a tag beside the test. Discovery supplies
# only a validated test ID, file, and Vitest project; this E2E workflow owns
# the shared job's runner, setup, timeout, permissions, and artifact policy.
shared-e2e:
name: Shared E2E (${{ matrix.id }})
needs: generate-matrix
if: ${{ needs.generate-matrix.outputs.test_matrix != '[]' }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.generate-matrix.outputs.test_matrix) }}
env:
CHECK_DOC_LINKS_REMOTE: "0"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/${{ matrix.id }}
E2E_TARGET_ID: ${{ matrix.id }}
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_RUN_LIVE_E2E: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run tagged credential-free test
env:
TEST_FILE: ${{ matrix.file }}
TEST_PROJECT: ${{ matrix.project }}
run: |
set -euo pipefail
npx vitest run --project "${TEST_PROJECT}" "${TEST_FILE}" \
--tags-filter=e2e/credential-free \
--silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts
- name: Upload test artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
openshell-gateway-auth-contract:
needs: generate-matrix
# Accepted release-gate tradeoff: this resource-heavy live probe remains
# selective because regular PR CI enforces the generated auth/JWT config and
# package-service fail-closed boundary in focused unit tests. Affected PRs
# must explicitly dispatch this job and record its result before merge.
if: ${{ contains(format(',{0},', inputs.jobs), ',openshell-gateway-auth-contract,') || contains(format(',{0},', inputs.targets), ',openshell-gateway-auth-contract,') }}
runs-on: ubuntu-latest
timeout-minutes: 20
env:
E2E_JOB: "1"
E2E_DEFAULT_ENABLED: "0"
E2E_TARGET_ID: "openshell-gateway-auth-contract"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openshell-gateway-auth-contract
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_OPENSHELL_PIN_VERSION: "0.0.85"
DOCKER_GRPC_PROBE_IMAGE: "node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Pre-pull pinned gateway auth probe image
run: docker pull "$DOCKER_GRPC_PROBE_IMAGE"
- name: Run OpenShell gateway auth contract live test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell-gateway >/dev/null 2>&1; then
OPENSHELL_GATEWAY_BIN="$(command -v openshell-gateway)"
elif [ -x "$HOME/.local/bin/openshell-gateway" ]; then
OPENSHELL_GATEWAY_BIN="$HOME/.local/bin/openshell-gateway"
else
echo "::error::OpenShell gateway binary not found after install"
ls -la /usr/local/bin/openshell-gateway "$HOME/.local/bin/openshell-gateway" 2>&1 || true
exit 1
fi
export OPENSHELL_GATEWAY_BIN
echo "Using OPENSHELL_GATEWAY_BIN=$OPENSHELL_GATEWAY_BIN"
"$OPENSHELL_GATEWAY_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openshell-gateway-auth-source-contract.test.ts
- name: Upload OpenShell gateway auth contract artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
mcp-bridge:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',mcp-bridge,') || contains(format(',{0},', inputs.targets), ',mcp-bridge,') }}
runs-on: ubuntu-latest
permissions:
contents: read
# Keep each destructive agent lifecycle on a fresh runner. This bounds the
# blast radius of runner loss and gives every adapter independent evidence.
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
agent: [openclaw, hermes, deepagents]
env:
E2E_JOB: "1"
E2E_TARGET_ID: "mcp-bridge"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/mcp-bridge/${{ matrix.agent }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_E2E_SHARD: ${{ matrix.agent }}
NEMOCLAW_MCP_BRIDGE_AGENT: ${{ matrix.agent }}
NEMOCLAW_OPENSHELL_CHANNEL: stable
NEMOCLAW_OPENSHELL_EXACT_MAIN_PROOF: "1"
NEMOCLAW_RUN_LIVE_E2E: "1"
OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:f4226253a3525c3832adac5b38b419a0f27d1e915effe565b5885e20f93cd5e9
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install and verify cloudflared prerequisite
# Update posture: maintainers review upstream cloudflared releases and
# update the version and reviewed SHA256 together in both explicit MCP
# lanes; mutable package repositories and unreviewed latest releases
# are intentionally rejected by the workflow-contract tests.
env:
CLOUDFLARED_VERSION: "2026.6.1"
CLOUDFLARED_DEB_SHA256: "ccd02ec216c62bfa573395d8f72cb2e91e95cbdf8726a8acc06b3e2d9aa31526"
run: |
set -euo pipefail
cloudflared_deb="${RUNNER_TEMP}/cloudflared-${CLOUDFLARED_VERSION}-linux-amd64.deb"
curl -fL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb" -o "${cloudflared_deb}"
printf '%s %s\n' "${CLOUDFLARED_DEB_SHA256}" "${cloudflared_deb}" | sha256sum -c -
package="$(dpkg-deb -f "${cloudflared_deb}" Package)"
version="$(dpkg-deb -f "${cloudflared_deb}" Version)"
architecture="$(dpkg-deb -f "${cloudflared_deb}" Architecture)"
if [[ "${package}" != "cloudflared" || "${version}" != "${CLOUDFLARED_VERSION}" || "${architecture}" != "amd64" ]]; then
printf 'Unexpected cloudflared package metadata: package=%s version=%s architecture=%s\n' "${package}" "${version}" "${architecture}" >&2
exit 1
fi
sudo dpkg -i "${cloudflared_deb}"
cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}"
- name: Generate MCP test TLS
run: bash test/e2e/setup-mcp-test-tls.sh
- name: Install OpenShell CLI
env:
NEMOCLAW_OPENSHELL_FORCE_INSTALL: "1"
run: |
set -euo pipefail
bash scripts/install-openshell.sh
if [[ "$NEMOCLAW_MCP_BRIDGE_AGENT" == "deepagents" ]]; then
mkdir -p "$E2E_ARTIFACT_DIR/mcp-bridge-deepagents"
jq -n '{
schemaVersion: 1,
sourceRepository: "NVIDIA/OpenShell",
releaseTag: "v0.0.85",
sourceSha: "3dee5570a46076a57a3b056f35f35ebc0861ac85",
artifacts: {
cli: {binarySha256: "222d9d53a142691d7a7de2c692f38e52d24066f9f633d53746c5fef775861bc8"},
gateway: {binarySha256: "33bb479d936c3c1b17dd475df05747be9de74564fb67d69a4c33cdd01181d02f"},
standaloneSandbox: {binarySha256: "863ef21ab7ef623f5e7a8728c4e5532b46bfbae3ace3b800665a1c6353a1f7d2"}
}
}' > "$E2E_ARTIFACT_DIR/mcp-bridge-deepagents/openshell-exact-main-provenance.json"
fi
- name: Run MCP OpenShell provider live test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/mcp-bridge.test.ts
if [[ "$NEMOCLAW_MCP_BRIDGE_AGENT" == "deepagents" ]]; then
npx vitest run --project e2e-live \
test/e2e/live/openshell-credential-generation-window.test.ts \
-t '^openshell-credential-generation-window$' \
--no-file-parallelism \
--silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts
fi
- id: mcp_artifact_secret_scan
name: Scan MCP artifacts for fixture credentials
if: always()
run: >-
npx tsx tools/e2e/assert-mcp-artifact-secrets-absent.mts
e2e-artifacts/live/mcp-bridge/${{ matrix.agent }}
- name: Upload MCP server artifacts
if: ${{ always() && steps.mcp_artifact_secret_scan.outcome == 'success' }}
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-mcp-bridge-${{ matrix.agent }}
path: e2e-artifacts/live/mcp-bridge/${{ matrix.agent }}/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
mcp-bridge-dev:
needs: generate-matrix
# Moving OpenShell dev artifacts are compatibility evidence only and must
# never enter scheduled or default manual runs without explicit selection.
if: ${{ contains(format(',{0},', inputs.jobs), ',mcp-bridge-dev,') || contains(format(',{0},', inputs.targets), ',mcp-bridge-dev,') }}
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
agent: [openclaw, hermes, deepagents]
env:
E2E_JOB: "1"
E2E_DEFAULT_ENABLED: "0"
E2E_TARGET_ID: "mcp-bridge-dev"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/mcp-bridge-dev/${{ matrix.agent }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_E2E_SHARD: ${{ matrix.agent }}
NEMOCLAW_MCP_BRIDGE_AGENT: ${{ matrix.agent }}
NEMOCLAW_OPENSHELL_CHANNEL: dev
NEMOCLAW_RUN_LIVE_E2E: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install and verify cloudflared prerequisite
# Update posture: keep this dev compatibility lane on the same reviewed
# version/SHA256 pair as the stable lane; workflow-contract tests fail
# if the pins diverge or installation becomes mutable.
env:
CLOUDFLARED_VERSION: "2026.6.1"
CLOUDFLARED_DEB_SHA256: "ccd02ec216c62bfa573395d8f72cb2e91e95cbdf8726a8acc06b3e2d9aa31526"
run: |
set -euo pipefail
cloudflared_deb="${RUNNER_TEMP}/cloudflared-${CLOUDFLARED_VERSION}-linux-amd64.deb"
curl -fL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb" -o "${cloudflared_deb}"
printf '%s %s\n' "${CLOUDFLARED_DEB_SHA256}" "${cloudflared_deb}" | sha256sum -c -
package="$(dpkg-deb -f "${cloudflared_deb}" Package)"
version="$(dpkg-deb -f "${cloudflared_deb}" Version)"
architecture="$(dpkg-deb -f "${cloudflared_deb}" Architecture)"
if [[ "${package}" != "cloudflared" || "${version}" != "${CLOUDFLARED_VERSION}" || "${architecture}" != "amd64" ]]; then
printf 'Unexpected cloudflared package metadata: package=%s version=%s architecture=%s\n' "${package}" "${version}" "${architecture}" >&2
exit 1
fi
sudo dpkg -i "${cloudflared_deb}"
cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}"
- name: Generate MCP test TLS
run: bash test/e2e/setup-mcp-test-tls.sh
- name: Revoke Docker auth before unverified dev tooling
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
- name: Install OpenShell CLI
env:
NEMOCLAW_ACCEPT_DEV_UNVERIFIED_INSTALL: "1"
NEMOCLAW_OPENSHELL_FORCE_INSTALL: "1"
run: |
set -euo pipefail
bash scripts/install-openshell.sh
- id: mcp_runtime_compatibility
name: Classify OpenShell credential-boundary compatibility
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
npx tsx tools/e2e/mcp-bridge-runtime-compatibility.mts
- name: Run MCP OpenShell provider live test
if: ${{ steps.mcp_runtime_compatibility.outputs.mode == 'full-lifecycle' }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/mcp-bridge.test.ts
- id: mcp_artifact_secret_scan
name: Scan MCP artifacts for fixture credentials
if: always()
run: >-
npx tsx tools/e2e/assert-mcp-artifact-secrets-absent.mts
e2e-artifacts/live/mcp-bridge-dev/${{ matrix.agent }}
- name: Upload MCP server artifacts
if: ${{ always() && steps.mcp_artifact_secret_scan.outcome == 'success' }}
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-mcp-bridge-dev-${{ matrix.agent }}
path: e2e-artifacts/live/mcp-bridge-dev/${{ matrix.agent }}/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
# Live skill injection and real OpenClaw agent-turn contract.
skill-agent:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',skill-agent,') || contains(format(',{0},', inputs.targets), ',skill-agent,') }}
runs-on: ubuntu-latest
timeout-minutes: 30
env:
E2E_JOB: "1"
E2E_TARGET_ID: "skill-agent"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/skill-agent
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
# The migrated skill-agent lane invokes helper scripts that call
# `openshell` after the repo CLI onboard command has returned. Install
# and resolve OpenShell in the workflow parent so PATH/OPENSHELL_BIN is
# available to both Vitest and the bash helpers.
run: bash scripts/install-openshell.sh
- name: Run skill-agent live test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/skill-agent.test.ts
- name: Upload skill-agent artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-skill-agent
path: |
e2e-artifacts/live/skill-agent/*/artifact-summary.json
e2e-artifacts/live/skill-agent/*/cleanup.json
e2e-artifacts/live/skill-agent/*/cleanup-skill-agent-summary.json
e2e-artifacts/live/skill-agent/*/target.json
e2e-artifacts/live/skill-agent/*/target-result.json
e2e-artifacts/live/skill-agent/*/test-progress.json
e2e-artifacts/live/skill-agent/*/shell/*.result.json
e2e-artifacts/live/skill-agent/*/shell/*.stdout.txt
e2e-artifacts/live/skill-agent/*/shell/*.stderr.txt
# Direct OpenClaw skills CLI contract.
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
openclaw-skill-cli:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-skill-cli,') || contains(format(',{0},', inputs.targets), ',openclaw-skill-cli,') }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openclaw-skill-cli"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-skill-cli
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-openclaw-skill-cli"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run OpenClaw skill CLI live test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-skill-cli.test.ts
- name: Upload OpenClaw skill CLI artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
inference-routing:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',inference-routing,') || contains(format(',{0},', inputs.targets), ',inference-routing,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "inference-routing"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/inference-routing
NEMOCLAW_RUN_LIVE_E2E: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run inference routing live test
# Direct E2E coverage. The always-on PR-safe slices prove invalid-key,
# unreachable-endpoint, and localhost-compatible gateway routing
# without spending live provider quota. Credential-backed isolation
# and provider smokes live in inference-routing-provider-smoke.test.ts;
# any future secret-bearing lane must run that file from trusted main.
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/inference-routing.test.ts
- name: Upload inference routing artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
cloud-inference:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',cloud-inference,') || contains(format(',{0},', inputs.targets), ',cloud-inference,') }}
runs-on: ubuntu-latest
timeout-minutes: 50
env:
E2E_JOB: "1"
E2E_TARGET_ID: "cloud-inference"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/cloud-inference
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-cloud-inference"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run cloud inference live test
# The Vitest test
# preserves the legacy install.sh/onboard path, posts a live PONG
# request through sandbox inference.local, and reuses the skill
# filesystem validators from the retained bash helpers.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/cloud-inference.test.ts
- name: Upload cloud inference artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
gpu-e2e:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',gpu-e2e,') || contains(format(',{0},', inputs.targets), ',gpu-e2e,') }}
runs-on: linux-amd64-gpu-rtxpro6000-latest-1
# The 55 minute install command includes a cold 40 minute model pull;
# 35 minutes remain for runner setup, assertions, artifacts, and cleanup.
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "gpu-e2e"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/gpu-e2e
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_MODEL: "qwen3.5:9b"
NEMOCLAW_PROVIDER: "ollama"
NEMOCLAW_OLLAMA_PULL_TIMEOUT: "2400"
NEMOCLAW_SANDBOX_NAME: "e2e-gpu-ollama"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run GPU Ollama live Vitest test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/gpu-e2e.test.ts
- name: Upload GPU E2E artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
agent-turn-latency:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',agent-turn-latency,') || contains(format(',{0},', inputs.targets), ',agent-turn-latency,') }}
runs-on: ubuntu-latest
timeout-minutes: 110
env:
E2E_JOB: "1"
E2E_TARGET_ID: "agent-turn-latency"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/agent-turn-latency
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
OPENSHELL_GATEWAY: "nemoclaw"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_PROVIDER: custom
NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1
NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_PREFERRED_API: openai-completions
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run agent turn latency live Vitest test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
COMPATIBLE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/agent-turn-latency.test.ts
- name: Upload agent turn latency artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
kimi-inference-compat:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',kimi-inference-compat,') || contains(format(',{0},', inputs.targets), ',kimi-inference-compat,') }}
runs-on: ubuntu-latest
timeout-minutes: 50
env:
E2E_JOB: "1"
E2E_TARGET_ID: "kimi-inference-compat"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/kimi-inference-compat
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-kimi-compat"
NEMOCLAW_E2E_INFERENCE_MODE: "mock"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run Kimi compatibility live Vitest test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
export OPENSHELL_BIN="$(command -v openshell || true)"
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/kimi-inference-compat.test.ts
- name: Upload Kimi compatibility artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
hermes-inference-switch:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',hermes-inference-switch,') || contains(format(',{0},', inputs.targets), ',hermes-inference-switch,') }}
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 55
strategy:
fail-fast: false
matrix:
include:
- mode: hosted
sandbox_name: e2e-hermes-inference-switch
switch_provider: nvidia-prod
switch_model: nvidia/nemotron-3-super-120b-a12b
switch_inference_api: openai-completions
switch_mock_anthropic: "0"
- mode: anthropic
sandbox_name: e2e-hermes-anthropic-inference-switch
switch_provider: compatible-anthropic-endpoint
switch_model: mock-anthropic-model
switch_inference_api: anthropic-messages
switch_mock_anthropic: "1"
env:
E2E_JOB: "1"
E2E_TARGET_ID: "hermes-inference-switch"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-inference-switch/${{ matrix.mode }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: "hermes"
NEMOCLAW_E2E_SHARD: ${{ matrix.mode }}
NEMOCLAW_SANDBOX_NAME: ${{ matrix.sandbox_name }}
NEMOCLAW_SWITCH_PROVIDER: ${{ matrix.switch_provider }}
NEMOCLAW_SWITCH_MODEL: ${{ matrix.switch_model }}
NEMOCLAW_SWITCH_INFERENCE_API: ${{ matrix.switch_inference_api }}
NEMOCLAW_SWITCH_MOCK_ANTHROPIC: ${{ matrix.switch_mock_anthropic }}
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run Hermes inference switch live Vitest test
env:
NVIDIA_API_KEY: ${{ matrix.mode == 'hosted' && secrets.NVIDIA_API_KEY || '' }}
NVIDIA_INFERENCE_API_KEY: ${{ matrix.mode == 'hosted' && secrets.NVIDIA_INFERENCE_API_KEY || '' }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
export OPENSHELL_BIN="$(command -v openshell || true)"
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-inference-switch.test.ts
- name: Upload Hermes inference switch artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-hermes-inference-switch-${{ matrix.mode }}
path: e2e-artifacts/live/hermes-inference-switch/${{ matrix.mode }}/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
brave-search:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',brave-search,') || contains(format(',{0},', inputs.targets), ',brave-search,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "brave-search"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/brave-search
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-brave-search"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run Brave search live Vitest test
env:
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/brave-search.test.ts
- name: Upload Brave search artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
ollama-auth-proxy:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',ollama-auth-proxy,') || contains(format(',{0},', inputs.targets), ',ollama-auth-proxy,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "ollama-auth-proxy"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/ollama-auth-proxy
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_OLLAMA_PORT: "11434"
NEMOCLAW_E2E_OLLAMA_PROXY_PORT: "11435"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
with:
build-cli: "false"
- name: Run Ollama auth proxy live Vitest test
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/ollama-auth-proxy.test.ts
- name: Upload Ollama auth proxy artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
cron-preflight-inference-local:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',cron-preflight-inference-local,') || contains(format(',{0},', inputs.targets), ',cron-preflight-inference-local,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "cron-preflight-inference-local"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/cron-preflight-inference-local
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-cron-preflight"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run cron preflight inference.local live Vitest test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/cron-preflight-inference-local.test.ts
- name: Upload cron preflight inference.local artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
issue-4434-tui-unreachable-inference:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',issue-4434-tui-unreachable-inference,') || contains(format(',{0},', inputs.targets), ',issue-4434-tui-unreachable-inference,') }}
runs-on: ubuntu-latest
timeout-minutes: 120
env:
E2E_JOB: "1"
E2E_TARGET_ID: "issue-4434-tui-unreachable-inference"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/issue-4434-tui-unreachable-inference
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_ISSUE_4434_LIVE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
# This free-standing job checks out and executes the PR target ref. Its
# privileged host dependency setup loads the first-party
# host-dependency-setup action pinned to an immutable full SHA, so the
# target ref never runs sudo with its own code. Only expect and iptables
# are allowed here: the TUI driver and egress-isolation assertion require
# them. Workflow contract tests pin the action provenance and package list.
- name: "Install issue #4434 host dependencies"
uses: NVIDIA/NemoClaw/.github/actions/host-dependency-setup@4def1501b34ce586f83b91af50a66b5d22b31d75
with:
packages: expect iptables
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: "Run issue #4434 TUI unreachable inference live test"
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/issue-4434-tui-unreachable-inference.test.ts
- name: "Upload issue #4434 TUI unreachable inference artifacts"
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
credential-sanitization:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',credential-sanitization,') || contains(format(',{0},', inputs.targets), ',credential-sanitization,') }}
runs-on: ubuntu-latest
timeout-minutes: 65
env:
E2E_JOB: "1"
E2E_TARGET_ID: "credential-sanitization"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/credential-sanitization
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-credential-sanitization"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run credential sanitization live test
# Preserves the
# same ubuntu-latest + Docker/OpenShell + NVIDIA_INFERENCE_API_KEY lane by running
# install.sh, onboarding a real sandbox, and probing sandbox state from
# Vitest while fixture redaction owns evidence logs.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/credential-sanitization.test.ts
- name: Upload credential sanitization artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
credential-migration:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',credential-migration,') || contains(format(',{0},', inputs.targets), ',credential-migration,') }}
runs-on: ubuntu-latest
timeout-minutes: 50
env:
E2E_JOB: "1"
E2E_TARGET_ID: "credential-migration"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/credential-migration
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-cred-migration"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run credential migration live test
# This live test
# stages NVIDIA_INFERENCE_API_KEY through legacy credentials.json as the
# custom provider's COMPATIBLE_API_KEY. The hosted service behind this
# repo-scoped secret is inference-api.nvidia.com, not Build/NVIDIA
# Endpoints, so the test must exercise the compatible-provider route.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
NEMOCLAW_PROVIDER: custom
NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1
NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_PREFERRED_API: openai-completions
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/credential-migration.test.ts
- name: Upload credential migration artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
sessions-agents-cli:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',sessions-agents-cli,') || contains(format(',{0},', inputs.targets), ',sessions-agents-cli,') }}
runs-on: ubuntu-latest
timeout-minutes: 70
env:
E2E_JOB: "1"
E2E_TARGET_ID: "sessions-agents-cli"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/sessions-agents-cli
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-sessions-agents-cli"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN \
bash scripts/install-openshell.sh
- name: Run sessions/agents CLI live test
# This stays as a
# host-side CLI live test because the contract is argv translation,
# OpenClaw gateway dispatch, pending pairing/scope approval, and JSON
# envelope handling for sessions/agents commands.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sessions-agents-cli.test.ts
- name: Upload sessions/agents CLI artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
hermes-slack:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',hermes-slack,') || contains(format(',{0},', inputs.targets), ',hermes-slack,') }}
runs-on: linux-amd64-cpu4
timeout-minutes: 75
env:
E2E_JOB: "1"
E2E_TARGET_ID: "hermes-slack"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-slack-e2e
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: hermes
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_POLICY_TIER: open
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_SANDBOX_NAME: e2e-hermes-slack
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run Hermes Slack live test
# Preserves the
# linux-amd64-cpu4 Docker/OpenShell/Hermes Slack policy, placeholder,
# provider, secret-boundary, and Python Slack egress contracts.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
SLACK_BOT_TOKEN: xoxb-test-hermes-slack-token
SLACK_APP_TOKEN: xapp-test-hermes-slack-app-token
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-slack-e2e.test.ts
- name: Upload Hermes Slack artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-hermes-slack
path: e2e-artifacts/live/hermes-slack-e2e/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
hermes-e2e:
needs: generate-matrix
if: ${{ needs.generate-matrix.outputs.hermes_selected == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 75
env:
E2E_JOB: "1"
E2E_TARGET_ID: "hermes-e2e"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-e2e
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_INFERENCE_MODE: ${{ inputs.inference_mode || 'mock' }}
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: hermes
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_SANDBOX_NAME: e2e-hermes
NEMOCLAW_ONBOARD_VALIDATION_TIMEOUT_SECONDS: "60"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run Hermes live Vitest test
env:
NVIDIA_INFERENCE_API_KEY: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && inputs.checkout_sha == '' && (inputs.inference_mode || 'mock') != 'mock' && secrets.NVIDIA_INFERENCE_API_KEY || '' }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-e2e.test.ts
- name: Upload Hermes live Vitest artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
hermes-gpu-startup:
needs: generate-matrix
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && (contains(format(',{0},', inputs.jobs), ',hermes-gpu-startup,') || contains(format(',{0},', inputs.targets), ',hermes-gpu-startup,')) }}
runs-on: linux-amd64-gpu-rtxpro6000-latest-1
timeout-minutes: 90
strategy:
fail-fast: false
max-parallel: 1
matrix:
include:
- scenario: native
- scenario: fallback
- scenario: compatibility-only
env:
E2E_JOB: "1"
E2E_TARGET_ID: "hermes-gpu-startup"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-gpu-startup/${{ matrix.scenario }}
E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }}
NEMOCLAW_E2E_SHARD: ${{ matrix.scenario }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: hermes
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_SANDBOX_GPU: "1"
NEMOCLAW_SANDBOX_NAME: e2e-hermes-gpu-startup-${{ matrix.scenario }}
NEMOCLAW_ONBOARD_VALIDATION_TIMEOUT_SECONDS: "60"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- name: Checkout trusted Hermes GPU runtime fixture
if: ${{ matrix.scenario == 'fallback' }}
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
repository: NVIDIA/NemoClaw
ref: ${{ github.workflow_sha }}
path: .trusted-hermes-gpu-fixture-${{ github.run_id }}-${{ github.run_attempt }}
sparse-checkout: tools/e2e/hermes-gpu-docker-runtime-fixture.sh
sparse-checkout-cone-mode: false
persist-credentials: false
- name: Install trusted Hermes GPU runtime fixture
if: ${{ matrix.scenario == 'fallback' }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }}
ENV: /dev/null
TRUSTED_DISPATCH_SHA: ${{ github.sha }}
TRUSTED_FIXTURE_SHA256: e273c4baa7fe89546d64517cf56eafec30aeda7b355971263605ab1327fade02
TRUSTED_WORKFLOW_SHA: ${{ github.workflow_sha }}
run: |
set -euo pipefail
trusted_checkout="$GITHUB_WORKSPACE/.trusted-hermes-gpu-fixture-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
trusted_source="$trusted_checkout/tools/e2e/hermes-gpu-docker-runtime-fixture.sh"
trusted_fixture="/usr/local/libexec/nemoclaw/hermes-gpu-docker-runtime-fixture.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.${E2E_HERMES_GPU_STARTUP_SCENARIO}"
trusted_state_root=/var/lib/nemoclaw-e2e
run_trusted_fixture() {
/usr/bin/sudo -n /usr/bin/env -i \
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
/bin/bash "$trusted_fixture" "$@"
}
[[ "$TRUSTED_WORKFLOW_SHA" =~ ^[a-f0-9]{40}$ ]] \
|| { echo "Trusted workflow SHA must be an immutable commit" >&2; exit 1; }
[[ "$TRUSTED_DISPATCH_SHA" = "$TRUSTED_WORKFLOW_SHA" ]] \
|| { echo "Trusted fixture must match the dispatched main workflow" >&2; exit 1; }
[[ "$TRUSTED_FIXTURE_SHA256" =~ ^[a-f0-9]{64}$ ]] \
|| { echo "Trusted fixture SHA-256 must be pinned" >&2; exit 1; }
[ "$(/usr/bin/git -C "$trusted_checkout" rev-parse HEAD)" = "$TRUSTED_WORKFLOW_SHA" ] \
|| { echo "Trusted fixture checkout does not match the workflow SHA" >&2; exit 1; }
[ -f "$trusted_source" ] && [ ! -L "$trusted_source" ] \
|| { echo "Trusted Docker fixture must be a regular non-symlink file" >&2; exit 1; }
/usr/bin/sudo /usr/bin/install -d -o root -g root -m 0755 /usr/local/libexec/nemoclaw
/usr/bin/sudo /usr/bin/install -o root -g root -m 0500 \
"$trusted_source" "$trusted_fixture"
[ "$(/usr/bin/sudo /usr/bin/stat -c '%a %u %g' "$trusted_fixture")" = "500 0 0" ] \
|| { echo "Trusted Docker fixture ownership or mode is invalid" >&2; exit 1; }
printf '%s %s\n' "$TRUSTED_FIXTURE_SHA256" "$trusted_fixture" \
| /usr/bin/sudo /usr/bin/sha256sum -c -
/usr/bin/sudo /usr/bin/cmp -s "$trusted_source" "$trusted_fixture" \
|| { echo "Installed Docker fixture does not match trusted workflow code" >&2; exit 1; }
/usr/bin/sudo /usr/bin/install -d -o root -g root -m 0700 "$trusted_state_root"
# Recover any root-owned snapshot left by a hard-cancelled earlier run before
# PR-controlled build or test code executes on this persistent GPU runner.
if ! /usr/bin/sudo /usr/bin/find "$trusted_state_root" -mindepth 1 -maxdepth 1 \
-type d -name 'hermes-gpu-fallback-docker-runtime.*' -print0 \
| while IFS= read -r -d '' stale_state_dir; do
if ! run_trusted_fixture restore \
"$stale_state_dir" /etc/docker/daemon.json >/dev/null; then
exit 1
fi
done; then
echo "Could not recover stale Docker fallback state" >&2
exit 1
fi
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Reassert trusted Node runtime
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0
env:
BASH_ENV: /dev/null
E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }}
ENV: /dev/null
NODE_OPTIONS: ""
with:
node-version: "22"
- name: Run Hermes GPU startup live Vitest test
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }}
ENV: /dev/null
run: |
set -euo pipefail
if [ "$E2E_HERMES_GPU_STARTUP_SCENARIO" = fallback ]; then
umask 077
daemon_json=/etc/docker/daemon.json
trusted_fixture="/usr/local/libexec/nemoclaw/hermes-gpu-docker-runtime-fixture.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.${E2E_HERMES_GPU_STARTUP_SCENARIO}"
trusted_state_root=/var/lib/nemoclaw-e2e
run_trusted_fixture() {
/usr/bin/sudo -n /usr/bin/env -i \
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
/bin/bash "$trusted_fixture" "$@"
}
mkdir -p "$E2E_ARTIFACT_DIR"
[ "$(/usr/bin/sudo /usr/bin/stat -c '%a %u %g' "$trusted_fixture")" = "500 0 0" ] \
|| { echo "Trusted Docker fixture ownership or mode changed" >&2; exit 1; }
/usr/bin/sudo /usr/bin/install -d -o root -g root -m 0700 "$trusted_state_root"
state_dir="$(/usr/bin/sudo /usr/bin/mktemp -d \
"$trusted_state_root/hermes-gpu-fallback-docker-runtime.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.fallback.XXXXXX")"
/usr/bin/sudo /usr/bin/chown root:root "$state_dir"
/usr/bin/sudo /usr/bin/chmod 0700 "$state_dir"
# SOURCE_OF_TRUTH_REVIEW (Hermes GPU fallback fixture; #6110):
# invalidState: a cancelled fallback test leaves the runner-global Docker default
# runtime changed, contaminating later OpenShell jobs on this self-hosted runner.
# sourceBoundary: daemon mutation uses only immutable root-owned workflow_sha
# code; the PR test cannot replace the helper or its root-owned snapshot state.
# whyNotSourceFix: production must fail closed when GPU attachment is ambiguous;
# this fixture needs a real no-GPU partial container on an nvidia-default runner.
# regressionTest: hermes-gpu-startup-workflow-boundary requires this same-step trap.
# removalCondition: remove the daemon fixture when OpenShell can create a provably
# GPU-unattached partial sandbox without consulting Docker's default runtime.
restore_docker_default_runtime() {
local command_status=$?
local restore_status=0
local restored_runtime=""
trap - EXIT INT TERM
set +e
restored_runtime="$(run_trusted_fixture restore "$state_dir" "$daemon_json")"
restore_status=$?
if [ "$restore_status" -eq 0 ] && [ -n "$restored_runtime" ]; then
printf '%s\n' "$restored_runtime" \
>"$E2E_ARTIFACT_DIR/docker-default-runtime-restored.txt" \
|| restore_status=1
fi
if [ "$restore_status" -ne 0 ]; then
exit 1
fi
exit "$command_status"
}
# OpenShell leaves Runtime unset after the wrapper strips --gpu, so Docker
# otherwise substitutes its daemon default. Force runc only within this
# trap-guarded process so production can prove the partial container has no GPU.
trap restore_docker_default_runtime EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
original_runtime="$(run_trusted_fixture capture "$state_dir" "$daemon_json")"
printf '%s\n' "$original_runtime" \
>"$E2E_ARTIFACT_DIR/docker-default-runtime-before.txt"
selected_runtime="$(run_trusted_fixture select-runc "$state_dir" "$daemon_json")"
printf '%s\n' "$selected_runtime" \
>"$E2E_ARTIFACT_DIR/docker-default-runtime-during.txt"
fi
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-gpu-startup.test.ts
- name: Recover Docker daemon after Hermes GPU fallback fixture
if: always()
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }}
ENV: /dev/null
run: |
set -euo pipefail
if [ "$E2E_HERMES_GPU_STARTUP_SCENARIO" != fallback ]; then
exit 0
fi
recovery_failed=0
restored_runtime=""
trusted_fixture="/usr/local/libexec/nemoclaw/hermes-gpu-docker-runtime-fixture.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.${E2E_HERMES_GPU_STARTUP_SCENARIO}"
trusted_state_root=/var/lib/nemoclaw-e2e
state_prefix="hermes-gpu-fallback-docker-runtime.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.fallback."
run_trusted_fixture() {
/usr/bin/sudo -n /usr/bin/env -i \
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
/bin/bash "$trusted_fixture" "$@"
}
if ! /usr/bin/sudo /usr/bin/find "$trusted_state_root" -mindepth 1 -maxdepth 1 \
-type d -name "${state_prefix}*" -print0 \
| while IFS= read -r -d '' state_dir; do
if ! restored_runtime="$(run_trusted_fixture restore \
"$state_dir" /etc/docker/daemon.json)"; then
exit 1
fi
if [ -n "$restored_runtime" ]; then
printf '%s\n' "$restored_runtime" \
>"$E2E_ARTIFACT_DIR/docker-default-runtime-restored.txt"
fi
done; then
recovery_failed=1
fi
if [ "$recovery_failed" -ne 0 ]; then
echo "Independent Docker daemon recovery could not prove restoration" >&2
exit 1
fi
- name: Remove trusted Hermes GPU runtime fixture
if: ${{ always() && matrix.scenario == 'fallback' }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
E2E_HERMES_GPU_STARTUP_SCENARIO: ${{ matrix.scenario }}
ENV: /dev/null
run: |
set -euo pipefail
trusted_fixture="/usr/local/libexec/nemoclaw/hermes-gpu-docker-runtime-fixture.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.${E2E_HERMES_GPU_STARTUP_SCENARIO}"
/usr/bin/sudo /usr/bin/rm -f -- "$trusted_fixture"
- name: Upload Hermes GPU startup artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-hermes-gpu-startup-${{ matrix.scenario }}
path: e2e-artifacts/live/hermes-gpu-startup/${{ matrix.scenario }}/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
hermes-dashboard:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',hermes-dashboard,') || contains(format(',{0},', inputs.targets), ',hermes-dashboard,') }}
runs-on: ubuntu-latest
timeout-minutes: 75
env:
E2E_JOB: "1"
E2E_TARGET_ID: "hermes-dashboard"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-dashboard
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: hermes
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_SANDBOX_NAME: e2e-hermes-dashboard
NEMOCLAW_E2E_HERMES_DASHBOARD: "1"
NEMOCLAW_ONBOARD_VALIDATION_TIMEOUT_SECONDS: "60"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run Hermes dashboard live Vitest test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-e2e.test.ts
- name: Upload Hermes dashboard live Vitest artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
hermes-discord:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',hermes-discord,') || contains(format(',{0},', inputs.targets), ',hermes-discord,') }}
runs-on: ubuntu-latest
timeout-minutes: 75
env:
E2E_JOB: "1"
E2E_TARGET_ID: "hermes-discord"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-discord
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_SANDBOX_NAME: e2e-hermes-discord
NEMOCLAW_AGENT: hermes
NEMOCLAW_POLICY_TIER: open
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run Hermes Discord live test
# Preserves the
# ubuntu-latest Docker/OpenShell/Hermes Discord schema, provider,
# placeholder isolation, native gateway rewrite, and rebuild contracts.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
DISCORD_BOT_TOKEN: test-fake-discord-token-hermes-e2e
DISCORD_SERVER_IDS: "1491590992753590594"
DISCORD_ALLOWED_IDS: "1005536447329222676"
DISCORD_REQUIRE_MENTION: "0"
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-discord.test.ts
- name: Upload Hermes Discord artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
network-policy:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',network-policy,') || contains(format(',{0},', inputs.targets), ',network-policy,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "network-policy"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/network-policy
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
# Raw OpenShell sandbox commands in the migrated live test must target
# the gateway registered by NemoClaw onboarding even when OpenShell has
# no active gateway selected on the runner.
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
# Expect is a reviewed host-tool consumer for the interactive policy-add
# test. This job executes a selected target ref, so privileged setup runs
# from the first-party host-dependency-setup action pinned to an immutable
# full SHA, never that ref. Only expect is allowed here; iptables is scoped
# to the issue #4434 egress-isolation job.
- name: Install network-policy host dependencies
uses: NVIDIA/NemoClaw/.github/actions/host-dependency-setup@4def1501b34ce586f83b91af50a66b5d22b31d75
with:
packages: expect
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
# Runs without workflow tokens, Docker credentials, or NVIDIA_INFERENCE_API_KEY.
# scripts/install-openshell.sh pins the OpenShell version and verifies
# release SHA-256 checksums before installation.
env:
NEMOCLAW_NON_INTERACTIVE: "1"
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run network-policy live test
# Free-standing live network policy allow/deny probes.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/network-policy.test.ts
- name: Upload network-policy artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
common-egress-agent:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',common-egress-agent,') || contains(format(',{0},', inputs.targets), ',common-egress-agent,') }}
runs-on: ubuntu-latest
timeout-minutes: 120
env:
E2E_JOB: "1"
E2E_TARGET_ID: "common-egress-agent"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/common-egress-agent
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_RECREATE_SANDBOX: "1"
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
# Direct Vitest execution uses bin/nemoclaw.js instead of install.sh,
# so install OpenShell explicitly before onboard and SSH-agent probes.
env:
NEMOCLAW_NON_INTERACTIVE: "1"
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run common-egress agent live test
# Preserves
# OpenClaw balanced/open and Hermes open common-egress agent-turn
# coverage against real sandbox policy and live hosted inference.
env:
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/common-egress-agent.test.ts
- name: Upload common-egress agent artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
shields-config:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',shields-config,') || contains(format(',{0},', inputs.targets), ',shields-config,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "shields-config"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/shields-config
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: e2e-shields
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
with:
build-cli: "false"
- name: Run shields-config live test
# The Vitest test runs
# bash install.sh to preserve installer/onboard fidelity, then probes
# real shields/config behavior against the live sandbox.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/shields-config.test.ts
- name: Upload shields-config artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-shields-config
path: |
e2e-artifacts/live/shields-config/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
hermes-shields-config:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',hermes-shields-config,') || contains(format(',{0},', inputs.targets), ',hermes-shields-config,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "hermes-shields-config"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-shields-config
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: hermes
NEMOCLAW_SANDBOX_NAME: e2e-hermes-shields
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run Hermes shields-config live regression
# This hermetic regression uses a local OpenAI-compatible endpoint and
# proves two complete down/up cycles on a fresh non-root Hermes sandbox.
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-shields-config.test.ts
- name: Upload Hermes shields-config artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
rebuild-openclaw:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',rebuild-openclaw,') || contains(format(',{0},', inputs.targets), ',rebuild-openclaw,') }}
runs-on: ubuntu-latest
timeout-minutes: 130
env:
E2E_JOB: "1"
E2E_TARGET_ID: "rebuild-openclaw"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/rebuild-openclaw
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
# Direct Vitest execution uses bin/nemoclaw.js instead of install.sh,
# so install OpenShell explicitly before onboard/rebuild commands.
env:
NEMOCLAW_NON_INTERACTIVE: "1"
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run OpenClaw rebuild live test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/rebuild-openclaw.test.ts
- name: Upload OpenClaw rebuild artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
rebuild-hermes:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',rebuild-hermes,') || contains(format(',{0},', inputs.targets), ',rebuild-hermes,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "rebuild-hermes"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/rebuild-hermes
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: hermes
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_PROVIDER: custom
NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1
NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_PREFERRED_API: openai-completions
NEMOCLAW_SANDBOX_NAME: e2e-rebuild-hermes
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
# Full NemoClaw installer coverage remains in hermes-e2e. Rebuild uses
# the checked-out CLI and installs only its external runtime dependency.
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN -u GH_TOKEN bash scripts/install-openshell.sh
- name: Add swap for Hermes image rebuild
shell: bash
run: |
set -euo pipefail
sudo fallocate -l 32G /mnt/nemoclaw-hermes-rebuild.swap
sudo chmod 0600 /mnt/nemoclaw-hermes-rebuild.swap
sudo mkswap /mnt/nemoclaw-hermes-rebuild.swap
sudo swapon /mnt/nemoclaw-hermes-rebuild.swap
swapon --show
- name: Run Hermes rebuild live test
# Preserves the checked-out CLI, Docker/OpenShell, Hermes base-image
# rebuild, registry, messaging-placeholder, and backup hygiene boundaries.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
mkdir -p "$E2E_ARTIFACT_DIR"
baseline_file="$E2E_ARTIFACT_DIR/runner-pressure-baseline.jsonl"
phase_baselines_file="$E2E_ARTIFACT_DIR/runner-pressure-phase-baselines.jsonl"
classification_file="$E2E_ARTIFACT_DIR/runner-pressure-classification.jsonl"
test_outcome_file="$E2E_ARTIFACT_DIR/live-test-outcome.json"
export DOCKER_OOM_CONTAINER="$NEMOCLAW_SANDBOX_NAME"
export E2E_PHASE="rebuild-hermes.workflow"
export E2E_RESOURCE_BASELINE_FILE="$baseline_file"
export E2E_RESOURCE_PHASE_BASELINES_FILE="$phase_baselines_file"
export E2E_TERMINAL_CLASSIFICATION_FILE="$classification_file"
export E2E_TEST_OUTCOME_FILE="$test_outcome_file"
npx tsx tools/e2e/runner-pressure.mts snapshot
npx tsx tools/e2e/runner-pressure.mts initialize-evidence
set +e
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/rebuild-hermes.test.ts
test_status="$?"
set -e
if [ "$test_status" -ne 0 ]; then
npx tsx tools/e2e/runner-pressure.mts classify
npx tsx tools/e2e/runner-pressure.mts validate-classification
fi
exit "$test_status"
- name: Upload Hermes rebuild artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
rebuild-hermes-stale-base:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',rebuild-hermes-stale-base,') || contains(format(',{0},', inputs.targets), ',rebuild-hermes-stale-base,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "rebuild-hermes-stale-base"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/rebuild-hermes-stale-base
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: hermes
NEMOCLAW_HERMES_STALE_BASE_REBUILD_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_PROVIDER: custom
NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1
NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_PREFERRED_API: openai-completions
NEMOCLAW_SANDBOX_NAME: e2e-rebuild-hermes-base
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
# Full NemoClaw installer coverage remains in hermes-e2e. Rebuild uses
# the checked-out CLI and installs only its external runtime dependency.
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN -u GH_TOKEN bash scripts/install-openshell.sh
- name: Add swap for Hermes image rebuild
shell: bash
run: |
set -euo pipefail
sudo fallocate -l 32G /mnt/nemoclaw-hermes-rebuild.swap
sudo chmod 0600 /mnt/nemoclaw-hermes-rebuild.swap
sudo mkswap /mnt/nemoclaw-hermes-rebuild.swap
sudo swapon /mnt/nemoclaw-hermes-rebuild.swap
swapon --show
- name: Run Hermes stale-base rebuild live test
# Uses NEMOCLAW_HERMES_STALE_BASE_REBUILD_E2E=1, preserving issue #3025's
# stale cached base-image regression boundary.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
mkdir -p "$E2E_ARTIFACT_DIR"
baseline_file="$E2E_ARTIFACT_DIR/runner-pressure-baseline.jsonl"
phase_baselines_file="$E2E_ARTIFACT_DIR/runner-pressure-phase-baselines.jsonl"
classification_file="$E2E_ARTIFACT_DIR/runner-pressure-classification.jsonl"
test_outcome_file="$E2E_ARTIFACT_DIR/live-test-outcome.json"
export DOCKER_OOM_CONTAINER="$NEMOCLAW_SANDBOX_NAME"
export E2E_PHASE="rebuild-hermes-stale-base.workflow"
export E2E_RESOURCE_BASELINE_FILE="$baseline_file"
export E2E_RESOURCE_PHASE_BASELINES_FILE="$phase_baselines_file"
export E2E_TERMINAL_CLASSIFICATION_FILE="$classification_file"
export E2E_TEST_OUTCOME_FILE="$test_outcome_file"
npx tsx tools/e2e/runner-pressure.mts snapshot
npx tsx tools/e2e/runner-pressure.mts initialize-evidence
set +e
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/rebuild-hermes.test.ts
test_status="$?"
set -e
if [ "$test_status" -ne 0 ]; then
npx tsx tools/e2e/runner-pressure.mts classify
npx tsx tools/e2e/runner-pressure.mts validate-classification
fi
exit "$test_status"
- name: Upload Hermes stale-base rebuild artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
sandbox-rebuild:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',sandbox-rebuild,') || contains(format(',{0},', inputs.targets), ',sandbox-rebuild,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "sandbox-rebuild"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/sandbox-rebuild
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
env:
NEMOCLAW_NON_INTERACTIVE: "1"
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run sandbox rebuild live test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-rebuild.test.ts
- name: Upload sandbox rebuild artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
sandbox-rlimits-connect:
needs: generate-matrix
if: ${{ contains(format(',{0},', inputs.jobs), ',sandbox-rlimits-connect,') || contains(format(',{0},', inputs.targets), ',sandbox-rlimits-connect,') }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
E2E_JOB: "1"
E2E_DEFAULT_ENABLED: "0"
E2E_TARGET_ID: "sandbox-rlimits-connect"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/sandbox-rlimits-connect
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_CONNECT_RLIMITS: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: e2e-rlimits-connect
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
env:
NEMOCLAW_NON_INTERACTIVE: "1"
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run sandbox rlimit connect live test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-rlimits-connect.test.ts
- name: Upload sandbox rlimit connect artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
overlayfs-autofix:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',overlayfs-autofix,') || contains(format(',{0},', inputs.targets), ',overlayfs-autofix,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "overlayfs-autofix"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/overlayfs-autofix
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: e2e-overlayfs
NEMOCLAW_E2E_TIMEOUT_SECONDS: "1500"
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run overlayfs autofix live test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/overlayfs-autofix.test.ts
- name: Upload overlayfs autofix artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
state-backup-restore:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',state-backup-restore,') || contains(format(',{0},', inputs.targets), ',state-backup-restore,') }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
E2E_JOB: "1"
E2E_TARGET_ID: "state-backup-restore"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/state-backup-restore
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: e2e-state-backup
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
env:
NEMOCLAW_NON_INTERACTIVE: "1"
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run state backup restore live test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/state-backup-restore.test.ts
- name: Upload state backup restore artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
upgrade-stale-sandbox:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',upgrade-stale-sandbox,') || contains(format(',{0},', inputs.targets), ',upgrade-stale-sandbox,') }}
runs-on: ubuntu-latest
timeout-minutes: 55
env:
E2E_JOB: "1"
E2E_TARGET_ID: "upgrade-stale-sandbox"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/upgrade-stale-sandbox
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-upgrade-stale"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN \
bash scripts/install-openshell.sh
- name: Run upgrade stale sandbox live Vitest test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/upgrade-stale-sandbox.test.ts
- name: Upload upgrade stale sandbox artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
double-onboard:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',double-onboard,') || contains(format(',{0},', inputs.targets), ',double-onboard,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "double-onboard"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/double-onboard
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run double-onboard live Vitest test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/double-onboard.test.ts
- name: Upload double-onboard Vitest artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
jetson-nvmap-gpu:
needs: generate-matrix
# Explicit-only until a stable Jetson runner is available; otherwise full-suite dispatches remain queued forever.
# Required validation path: dispatch with jobs=jetson-nvmap-gpu or targets=jetson-nvmap-gpu.
# Re-enable default dispatch only after a stable Jetson runner exists, then remove E2E_DEFAULT_ENABLED.
if: ${{ contains(format(',{0},', inputs.jobs), ',jetson-nvmap-gpu,') || contains(format(',{0},', inputs.targets), ',jetson-nvmap-gpu,') }}
# GitHub does not apply timeout-minutes until after runner assignment. Keep
# unconfirmed manual Jetson dispatches on a hosted runner so they fail fast
# instead of queueing forever on an offline self-hosted runner label.
runs-on: ${{ inputs.allow_jetson_runner_queue && (vars.JETSON_E2E_RUNNER_LABEL || 'linux-arm64-gpu-jetson-orin-latest-1') || 'ubuntu-latest' }}
timeout-minutes: 60
env:
E2E_JOB: "1"
E2E_DEFAULT_ENABLED: "0"
E2E_TARGET_ID: "jetson-nvmap-gpu"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/jetson-nvmap-gpu
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-jetson-nvmap"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_PROVIDER: "ollama"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- name: Guard Jetson runner dispatch
if: ${{ !inputs.allow_jetson_runner_queue }}
env:
JETSON_E2E_RUNNER_LABEL: ${{ vars.JETSON_E2E_RUNNER_LABEL || 'linux-arm64-gpu-jetson-orin-latest-1' }}
run: |
set -euo pipefail
echo "::error::jetson-nvmap-gpu was selected without allow_jetson_runner_queue=true."
echo "::error::GitHub Actions does not apply timeout-minutes while a self-hosted job is queued before runner assignment."
echo "::error::A repository administrator must confirm the runner is online in the authoritative NVIDIA/NemoClaw Settings -> Actions -> Runners inventory before enabling queueing."
echo "::error::Confirm an online runner with label ${JETSON_E2E_RUNNER_LABEL}, then re-run with allow_jetson_runner_queue=true."
exit 1
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Verify Jetson GPU availability
run: |
set -euo pipefail
echo "=== Tegra release ==="
cat /etc/nv_tegra_release 2>/dev/null || echo "(no /etc/nv_tegra_release)"
echo ""
echo "=== /dev/nvmap ==="
ls -l /dev/nvmap 2>/dev/null || echo "(no /dev/nvmap)"
echo ""
echo "=== Docker ==="
docker info --format '{{.ServerVersion}}'
docker info --format '{{json .Runtimes}}'
- name: Run Jetson nvmap GPU live Vitest test
# Keeps the
# Jetson/Tegra host, NVIDIA Docker runtime, install.sh, OpenShell
# sandbox exec, /dev/nvmap, CUDA cuInit(0), and status-proof boundary.
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/jetson-nvmap-gpu.test.ts
- name: Upload Jetson nvmap GPU artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
concurrent-gateway-ports:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',concurrent-gateway-ports,') || contains(format(',{0},', inputs.targets), ',concurrent-gateway-ports,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "concurrent-gateway-ports"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/concurrent-gateway-ports
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run concurrent gateway ports live Vitest test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/concurrent-gateway-ports.test.ts
- name: Upload concurrent gateway ports Vitest artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
onboard-resume:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',onboard-resume,') || contains(format(',{0},', inputs.targets), ',onboard-resume,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "onboard-resume"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/onboard-resume
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-resume"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_INFERENCE_API_KEY -u COMPATIBLE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run onboard-resume live Vitest test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/onboard-resume.test.ts
- name: Upload onboard-resume artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
full-e2e:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',full-e2e,') || contains(format(',{0},', inputs.targets), ',full-e2e,') }}
runs-on: ubuntu-latest
timeout-minutes: 75
env:
E2E_JOB: "1"
E2E_TARGET_ID: "full-e2e"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/full-e2e
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-full"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run full-e2e live Vitest test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/full-e2e.test.ts
- name: Upload full-e2e artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
security-posture:
permissions:
contents: read
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',security-posture,') || contains(format(',{0},', inputs.targets), ',security-posture,') }}
runs-on: ubuntu-latest
timeout-minutes: 75
strategy:
fail-fast: false
matrix:
include:
- agent: openclaw
sandbox_name: e2e-openclaw-security-posture
test_file: test/e2e/live/full-e2e.test.ts
- agent: hermes
sandbox_name: e2e-hermes-security-posture
test_file: test/e2e/live/hermes-e2e.test.ts
env:
E2E_JOB: "1"
E2E_TARGET_ID: "security-posture"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/security-posture-${{ matrix.agent }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: ${{ matrix.agent }}
# Legacy-parity contract: enforce a non-root host user. PID 1 must stay
# root long enough to step down child processes, so its uid, bounding
# set, and NoNewPrivs remain evidence unless an opt-in expectation is set.
NEMOCLAW_E2E_EXPECT_NON_ROOT_HOST: "1"
NEMOCLAW_E2E_SECURITY_POSTURE: "1"
NEMOCLAW_E2E_SHARD: ${{ matrix.agent }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ONBOARD_VALIDATION_TIMEOUT_SECONDS: "60"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_SANDBOX_NAME: ${{ matrix.sandbox_name }}
OPENSHELL_GATEWAY: nemoclaw
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run security posture live Vitest test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path "${{ matrix.test_file }}"
- name: Upload security posture artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-security-posture-${{ matrix.agent }}
path: e2e-artifacts/live/security-posture-${{ matrix.agent }}/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
cloud-onboard:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',cloud-onboard,') || contains(format(',{0},', inputs.targets), ',cloud-onboard,') }}
runs-on: ubuntu-latest
timeout-minutes: 70
env:
E2E_JOB: "1"
E2E_TARGET_ID: "cloud-onboard"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/cloud-onboard
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-cloud-onboard"
NEMOCLAW_PUBLIC_INSTALL_REF: "${{ inputs.checkout_sha || github.sha }}"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Configure cloud-onboard trace directory
shell: bash
run: |
set -euo pipefail
printf 'NEMOCLAW_TRACE_DIR=%s\n' "${RUNNER_TEMP}/nemoclaw-cloud-onboard-traces" >> "${GITHUB_ENV}"
# invalidState: the cloud-onboard DCode TUI check requires a PTY driver,
# but the fixed GitHub-hosted runner image does not provide expect.
# sourceBoundary: privileged host setup runs from the first-party
# host-dependency-setup action pinned to an immutable full SHA, never the
# repository target ref; the check only consumes expect without privilege.
# whyNotSourceFix: GitHub-hosted jobs cannot use a repository-owned host
# image, and caching privileged dpkg state between clean runners is not
# supported.
# regressionTest: workflow-boundary tests pin the ordering, action
# provenance, and package mapping.
# removalCondition: remove when the hosted runner supplies expect or the
# cloud-onboard acceptance check no longer requires a PTY.
- name: Install cloud-onboard DCode TUI host dependencies
uses: NVIDIA/NemoClaw/.github/actions/host-dependency-setup@4def1501b34ce586f83b91af50a66b5d22b31d75
with:
packages: expect
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run cloud-onboard live Vitest test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/cloud-onboard.test.ts
- name: Build trusted cloud-onboard timing summary
if: always()
shell: bash
run: |
set -euo pipefail
expected_trace_dir="${RUNNER_TEMP}/nemoclaw-cloud-onboard-traces"
if [ -z "${RUNNER_TEMP}" ] || [ "${NEMOCLAW_TRACE_DIR}" != "${expected_trace_dir}" ]; then
echo "::error::Refusing to sanitize unexpected raw trace path" >&2
exit 1
fi
python3 scripts/e2e/sanitize-trace-timing.py \
"${NEMOCLAW_TRACE_DIR}" \
"${E2E_ARTIFACT_DIR}"
# The target process must emit full local traces for diagnosis, but those
# traces may contain prompts, environment data, and credential material.
# Keep cleanup as a separate always() step so a sanitizer failure cannot
# bypass it. A runner-level termination is contained by the ephemeral
# GitHub-hosted runner.temp boundary. Remove this step only when the trace
# producer itself emits the allowlisted timing-only schema.
- name: Delete raw cloud-onboard traces
if: always()
shell: bash
run: |
set -euo pipefail
expected_trace_dir="${RUNNER_TEMP}/nemoclaw-cloud-onboard-traces"
if [ -z "${RUNNER_TEMP}" ] || [ "${NEMOCLAW_TRACE_DIR}" != "${expected_trace_dir}" ]; then
echo "::error::Refusing to delete unexpected raw trace path" >&2
exit 1
fi
rm -rf -- "${NEMOCLAW_TRACE_DIR}"
- name: Upload cloud-onboard artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
gpu-double-onboard:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',gpu-double-onboard,') || contains(format(',{0},', inputs.targets), ',gpu-double-onboard,') }}
runs-on: linux-amd64-gpu-rtxpro6000-latest-1
timeout-minutes: 100
env:
E2E_JOB: "1"
E2E_TARGET_ID: "gpu-double-onboard"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/gpu-double-onboard
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_MODEL: "qwen3.5:9b"
NEMOCLAW_SANDBOX_NAME: "e2e-gpu-double-onboard"
NEMOCLAW_PROVIDER: "ollama"
NEMOCLAW_OLLAMA_PROXY_PORT: "11435"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run gpu-double-onboard live Vitest test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/gpu-double-onboard.test.ts
- name: Upload gpu-double-onboard artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
onboard-repair:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',onboard-repair,') || contains(format(',{0},', inputs.targets), ',onboard-repair,') }}
runs-on: ubuntu-latest
timeout-minutes: 75
env:
E2E_JOB: "1"
E2E_TARGET_ID: "onboard-repair"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/onboard-repair
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-repair"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run onboard-repair live Vitest test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/onboard-repair.test.ts
- name: Upload onboard-repair artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
issue-4462-scope-upgrade-approval:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',issue-4462-scope-upgrade-approval,') || contains(format(',{0},', inputs.targets), ',issue-4462-scope-upgrade-approval,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "issue-4462-scope-upgrade-approval"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/issue-4462-scope-upgrade-approval
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-issue-4462"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run issue-4462-scope-upgrade-approval live Vitest test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/issue-4462-scope-upgrade-approval.test.ts
- name: Upload issue-4462-scope-upgrade-approval artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
token-rotation:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',token-rotation,') || contains(format(',{0},', inputs.targets), ',token-rotation,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "token-rotation"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/token-rotation
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run token rotation live test
# Preserve the original runner class: ubuntu-latest with Docker/OpenShell
# plus the fake OpenAI-compatible endpoint path and fake
# Telegram/Discord/Slack token boundary.
env:
GITHUB_TOKEN: ${{ github.token }}
TELEGRAM_BOT_TOKEN_A: "test-fake-token-A-rotation-e2e"
TELEGRAM_BOT_TOKEN_B: "test-fake-token-B-rotation-e2e"
DISCORD_BOT_TOKEN_A: "dc-a-rotation-e2e"
DISCORD_BOT_TOKEN_B: "dc-b-rotation-e2e"
SLACK_BOT_TOKEN_A: "xoxb-fake-A-rotation-e2e"
SLACK_BOT_TOKEN_B: "xoxb-fake-B-rotation-e2e"
SLACK_APP_TOKEN_A: "xapp-fake-A-rotation-e2e"
SLACK_APP_TOKEN_B: "xapp-fake-B-rotation-e2e"
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/token-rotation.test.ts
- name: Upload token rotation artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
messaging-compatible-endpoint:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',messaging-compatible-endpoint,') || contains(format(',{0},', inputs.targets), ',messaging-compatible-endpoint,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "messaging-compatible-endpoint"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/messaging-compatible-endpoint
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-msg-compat"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run messaging compatible endpoint live test
# Preserves the fake OpenAI-compatible endpoint, Telegram messaging
# config, inference.local, OpenClaw agent-turn, and proxy hop-header
# strip boundaries without relying on real messaging/provider secrets.
env:
NEMOCLAW_COMPAT_MOCK_API_KEY: "fake-compatible-key-e2e"
TELEGRAM_ALLOWED_IDS: "123456789"
TELEGRAM_BOT_TOKEN: "test-fake-telegram-token-e2e"
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/messaging-compatible-endpoint.test.ts
- name: Upload messaging compatible endpoint artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
openshell-gateway-upgrade:
name: OpenShell gateway upgrade (${{ matrix.id }})
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openshell-gateway-upgrade,') || contains(format(',{0},', inputs.targets), ',openshell-gateway-upgrade,') }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 70
strategy:
fail-fast: false
matrix:
include:
- id: v0.0.36-x86_64
runner: ubuntu-latest
shard: v0-0-36-x86-64
nemoclaw_ref: v0.0.36
nemoclaw_commit: "3351fbdd4eb7d9b80ec471545083956327da2b10"
installer_sha256: "0c42400a0d3867739f1d75d612e069967be4506e169974bbbebf14b7af39144f"
sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:104151ffadc2ff0b6c815e3c95c2783ced61aee0d0f83fc327cc02be9b7e14e6"
openshell_version: 0.0.36
openclaw_version: 2026.4.24
- id: v0.0.55-x86_64
runner: ubuntu-latest
shard: v0-0-55-x86-64
nemoclaw_ref: v0.0.55
nemoclaw_commit: "95d483fe2b6569d68e59493c60f19df09a068e8f"
installer_sha256: "ff8cf448e4d17b00421545a1f333262b615b1b0aa236d0cc5aeaf4e2cae2d897"
sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:10433a8cd2f2b809dd0fdf983514679e04c0f8aa1ff5bbff675029046033b108"
openshell_version: 0.0.44
openclaw_version: 2026.5.22
- id: v0.0.55-aarch64
runner: ubuntu-24.04-arm
shard: v0-0-55-aarch64
nemoclaw_ref: v0.0.55
nemoclaw_commit: "95d483fe2b6569d68e59493c60f19df09a068e8f"
installer_sha256: "ff8cf448e4d17b00421545a1f333262b615b1b0aa236d0cc5aeaf4e2cae2d897"
sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:10433a8cd2f2b809dd0fdf983514679e04c0f8aa1ff5bbff675029046033b108"
openshell_version: 0.0.44
openclaw_version: 2026.5.22
- id: v0.0.74-x86_64
runner: ubuntu-latest
shard: v0-0-74-x86-64
nemoclaw_ref: v0.0.74
nemoclaw_commit: "3a05b54e8ec3e1d5550ec5c728de54af872bffe3"
installer_sha256: "a0cd3feca488d247e53d59d7d8246d2b86e75e95acb5e7d78504b3c0c60fd7db"
sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:104151ffadc2ff0b6c815e3c95c2783ced61aee0d0f83fc327cc02be9b7e14e6"
openshell_version: 0.0.72
openclaw_version: 2026.5.27
- id: v0.0.89-x86_64
runner: ubuntu-latest
shard: v0-0-89-x86-64
nemoclaw_ref: v0.0.89
nemoclaw_commit: "1143aa5cce77f3bad1b3b5588bd7fddbe438237e"
installer_sha256: "00f24959e5ca68104fe91221c0a015dab6a4154618497fa36b969b661f418cc2"
sandbox_base_image_ref: "ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:3265d482f67c9d81ee3a59b0bbad5eb5ea6c705fea81ece8ae888ed12794f7f1"
openshell_version: 0.0.85
openclaw_version: 2026.6.10
current_openclaw_version: 2026.7.1
openclaw_state_upgrade: "1"
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openshell-gateway-upgrade"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openshell-gateway-upgrade
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_E2E_SHARD: ${{ matrix.shard }}
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_GATEWAY_UPGRADE_SURVIVOR_NAME: "e2e-gateway-upgrade-survivor"
NEMOCLAW_OLD_NEMOCLAW_REF: ${{ matrix.nemoclaw_ref }}
NEMOCLAW_OLD_NEMOCLAW_COMMIT: ${{ matrix.nemoclaw_commit }}
NEMOCLAW_OLD_INSTALLER_SHA256: ${{ matrix.installer_sha256 }}
NEMOCLAW_OLD_SANDBOX_BASE_IMAGE_REF: ${{ matrix.sandbox_base_image_ref }}
NEMOCLAW_OLD_OPENSHELL_VERSION: ${{ matrix.openshell_version }}
NEMOCLAW_OLD_OPENCLAW_VERSION: ${{ matrix.openclaw_version }}
NEMOCLAW_CURRENT_OPENCLAW_VERSION: ${{ matrix.current_openclaw_version }}
NEMOCLAW_OPENCLAW_STATE_UPGRADE_PROOF: ${{ matrix.openclaw_state_upgrade }}
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run OpenShell gateway upgrade live Vitest test
# Keep the original v0.0.36 fixture on x86_64 and validate the exact
# v0.0.55/OpenShell 0.0.44 regression shape on x86_64 and arm64, plus
# the immediate v0.0.74/OpenShell 0.0.72 predecessor to this bump. The
# v0.0.89 row proves OpenClaw 2026.6.10 state survives the 2026.7.1
# migration without crossing the OpenShell secret boundary.
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openshell-gateway-upgrade.test.ts
- name: Upload OpenShell gateway upgrade artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-openshell-gateway-upgrade-${{ matrix.id }}
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
messaging-providers:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',messaging-providers,') || contains(format(',{0},', inputs.targets), ',messaging-providers,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "messaging-providers"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/messaging-providers
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run messaging providers live Vitest test
# The test keeps
# the legacy fake-token defaults, optional _REAL secret overrides,
# provider placeholder redaction checks, and WhatsApp QR-only parity.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
TELEGRAM_BOT_TOKEN_REAL: ${{ secrets.TELEGRAM_BOT_TOKEN_REAL }}
DISCORD_BOT_TOKEN_REAL: ${{ secrets.DISCORD_BOT_TOKEN_REAL }}
SLACK_BOT_TOKEN_REAL: ${{ secrets.SLACK_BOT_TOKEN_REAL }}
SLACK_APP_TOKEN_REAL: ${{ secrets.SLACK_APP_TOKEN_REAL }}
TELEGRAM_ALLOWED_IDS: ${{ secrets.TELEGRAM_ALLOWED_IDS }}
TELEGRAM_AUTHORIZED_CHAT_IDS: ${{ secrets.TELEGRAM_AUTHORIZED_CHAT_IDS }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
TELEGRAM_CHAT_ID_E2E: ${{ secrets.TELEGRAM_CHAT_ID_E2E }}
DISCORD_CHANNEL_ID_E2E: ${{ secrets.DISCORD_CHANNEL_ID_E2E }}
SLACK_CHANNEL_ID_E2E: ${{ secrets.SLACK_CHANNEL_ID_E2E }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/messaging-providers.test.ts
- name: Upload messaging providers artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
bootstrap-install-smoke:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',bootstrap-install-smoke,') || contains(format(',{0},', inputs.targets), ',bootstrap-install-smoke,') }}
runs-on: ubuntu-latest
timeout-minutes: 30
env:
E2E_JOB: "1"
E2E_TARGET_ID: "bootstrap-install-smoke"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/bootstrap-install-smoke
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-bootstrap"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_PROVIDER: custom
NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1
NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_PREFERRED_API: openai-completions
SKIP_DOCKER_PULL: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
with:
build-cli: "false"
- name: Run bootstrap install smoke live test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
COMPATIBLE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/bootstrap-install-smoke.test.ts
- name: Upload bootstrap install smoke artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
# Provider-routed Model Router inference contract.
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
model-router-provider-routed-inference:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',model-router-provider-routed-inference,') || contains(format(',{0},', inputs.targets), ',model-router-provider-routed-inference,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "model-router-provider-routed-inference"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/model-router-provider-routed-inference
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run Model Router provider-routed inference live test
# Preserves the real provider-routed onboard, host model-router health, and
# sandbox inference.local completion boundaries without adding registry
# or migration-ledger wiring.
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/model-router-provider-routed-inference.test.ts
- name: Upload Model Router provider-routed inference artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
snapshot-commands:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',snapshot-commands,') || contains(format(',{0},', inputs.targets), ',snapshot-commands,') }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
E2E_JOB: "1"
E2E_TARGET_ID: "snapshot-commands"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/snapshot-commands
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-snapshot"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
with:
build-cli: "false"
- name: Run snapshot commands live test
# Drives install.sh and the real snapshot CLI lifecycle while the test
# supplies a sandbox-reachable fake OpenAI-compatible endpoint.
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/snapshot-commands.test.ts
- name: Upload snapshot commands artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
sandbox-operations:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',sandbox-operations,') || contains(format(',{0},', inputs.targets), ',sandbox-operations,') }}
runs-on: ubuntu-latest
# The live test receives 45 minutes for two onboards plus process/gateway
# recovery. The remaining 15 minutes cover checkout, build, OpenShell setup,
# artifact upload, and unconditional credential/resource cleanup.
timeout-minutes: 60
env:
E2E_JOB: "1"
E2E_TARGET_ID: "sandbox-operations"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/sandbox-operations
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
# Open permits the scenario's inference/log probes; TC-SBX-11 separately
# proves that sandbox-to-sandbox network isolation remains enforced.
NEMOCLAW_POLICY_TIER: "open"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Verify CLI launcher
run: |
test -x "${NEMOCLAW_CLI_BIN}"
"${NEMOCLAW_CLI_BIN}" --version
- name: Install OpenShell CLI
run: |
env -u DOCKER_CONFIG \
-u DOCKERHUB_USERNAME \
-u DOCKERHUB_TOKEN \
-u NVIDIA_API_KEY \
-u NVIDIA_INFERENCE_API_KEY \
-u GITHUB_TOKEN \
bash scripts/install-openshell.sh
- name: Run sandbox operations live test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-operations.test.ts
- name: Upload sandbox operations artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
sandbox-survival:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',sandbox-survival,') || contains(format(',{0},', inputs.targets), ',sandbox-survival,') }}
runs-on: ubuntu-latest
timeout-minutes: 30
env:
E2E_JOB: "1"
E2E_TARGET_ID: "sandbox-survival"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/sandbox-survival
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-survival"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run sandbox survival live test
# This intentionally
# runs install.sh from Vitest to preserve installer/OpenShell/onboard
# fidelity before exercising gateway restart, state survival, and live
# inference.local before and after restart.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-survival.test.ts
- name: Upload sandbox survival artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
diagnostics:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',diagnostics,') || contains(format(',{0},', inputs.targets), ',diagnostics,') }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
E2E_JOB: "1"
E2E_TARGET_ID: "diagnostics"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/diagnostics
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-diag"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run diagnostics live test
# This preserves the
# ubuntu-latest + Docker/OpenShell + NVIDIA_INFERENCE_API_KEY lane by running
# debug archives, install.sh/onboard, sandbox exec/status, and
# gateway-backed credentials list/reset from Vitest.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/diagnostics.test.ts
- name: Upload diagnostics artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
# Scheduled coverage for #6108 / #3513 / #3127. This exercises the complete
# managed v0.0.71 runtime with a custom plugin, then proves restart/rebuild
# persistence and target-side runtime-dependency replacement across devices.
openclaw-plugin-runtime-exdev:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-plugin-runtime-exdev,') || contains(format(',{0},', inputs.targets), ',openclaw-plugin-runtime-exdev,') }}
runs-on: ubuntu-latest
permissions:
contents: read
# Three bounded 25-minute onboards plus the 20-minute rebuild and 15-minute
# Vitest buffer need 110 minutes; allow 20 more for setup and teardown.
timeout-minutes: 130
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openclaw-plugin-runtime-exdev"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-plugin-runtime-exdev
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-openclaw-plugin-exdev"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Pre-pull release-matched Docker Hub builder image
shell: bash
run: |
set -euo pipefail
docker pull node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba
- name: Remove Docker auth before release-pinned fixture
if: always()
shell: bash
run: |
set -euo pipefail
bash .github/scripts/docker-auth-cleanup.sh
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run OpenClaw custom-plugin lifecycle and runtime-deps EXDEV live test
run: |
set -euo pipefail
test -n "${DOCKER_CONFIG:-}"
test ! -e "${DOCKER_CONFIG}"
test -z "${DOCKERHUB_USERNAME:-}"
test -z "${DOCKERHUB_TOKEN:-}"
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN \
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-plugin-runtime-exdev.test.ts
- name: Upload OpenClaw plugin runtime-deps EXDEV artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
# The #2603/#3145 OpenClaw websocket protocol/history contract.
openclaw-tui-chat-correlation:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-tui-chat-correlation,') || contains(format(',{0},', inputs.targets), ',openclaw-tui-chat-correlation,') }}
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 75
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openclaw-tui-chat-correlation"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-tui-chat-correlation
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
# This target runs the CLI built from this checkout; it does not use the public installer.
# Legacy target_ref dispatches were replaced by branch github.sha and validated checkout_sha runs.
NEMOCLAW_TUI_EXPECTED_CHECKOUT_SHA: "${{ inputs.checkout_sha || github.sha }}"
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_PROVIDER: custom
NEMOCLAW_ENDPOINT_URL: https://inference-api.nvidia.com/v1
NEMOCLAW_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_COMPAT_MODEL: nvidia/nvidia/nemotron-3-ultra
NEMOCLAW_PREFERRED_API: openai-completions
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
# The #6194 terminal regression uses Expect as its PTY driver. Privileged
# host setup runs from the first-party host-dependency-setup action pinned
# to an immutable full SHA: the selected target ref may consume expect but
# cannot expand the package allowlist or run sudo with its own code.
# invalidState: hosted-runner Ubuntu mirrors can fail transiently during update.
# sourceBoundary: only the pinned trusted action chooses the exact root-installed package.
# whyNotSourceFix: GitHub's Ubuntu image and configured repository move together, so a
# fixed package version would make the target brittle across routine runner refreshes.
# The runner's configured Ubuntu repository is therefore an accepted trust source.
# regressionTest: e2e-host-dependency-workflow-boundary rejects package or provenance drift.
# removalCondition: remove this step when the hosted image provides Expect itself.
- name: Install OpenClaw TUI host dependencies
uses: NVIDIA/NemoClaw/.github/actions/host-dependency-setup@4def1501b34ce586f83b91af50a66b5d22b31d75
with:
packages: expect
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run OpenClaw TUI chat correlation live test
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
COMPATIBLE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-tui-chat-correlation.test.ts
- name: Upload OpenClaw TUI chat correlation artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
# ── Free-standing recovery targets (#2701) ─────────────────────────
# Recovery / disruption targets don't fit the steady-state expected-state
# registry that drives `live` above. They run as free-standing
# Vitest test files using the same `e2e-live` project, framework
# fixtures, and live-project gate — just outside the matrix.
#
# First failing-test-first guard for #2701 (gateway recovery does not
# restore the /tmp guard chain after pod recreate). Will fail on `main`
# until the #2701 fix lands; flips green afterwards.
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
gateway-guard-recovery:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',gateway-guard-recovery,') || contains(format(',{0},', inputs.targets), ',gateway-guard-recovery,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "gateway-guard-recovery"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/gateway-guard-recovery
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
# nemoclaw onboard registers the gateway under the canonical name
# "nemoclaw" (src/lib/actions/sandbox/connect.ts:NEMOCLAW_GATEWAY_NAME)
# but does not call `openshell gateway select` to mark it active. The
# SandboxClient and recovery probes invoke `openshell sandbox exec`
# directly, which fails with "No active gateway" when no active
# gateway is configured. Setting OPENSHELL_GATEWAY here tells openshell
# to use the named gateway for every invocation (per `openshell` -h
# GATEWAY FLAGS: env: OPENSHELL_GATEWAY=).
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
# This target invokes `bin/nemoclaw.js onboard` directly, so install
# OpenShell explicitly just as install.sh would.
run: bash scripts/install-openshell.sh
- name: Run Vitest gateway-guard-recovery target
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
# OpenShell installs to /usr/local/bin on GitHub-hosted runners
# (writable by the runner user, no sudo) or to ~/.local/bin in
# NEMOCLAW_NON_INTERACTIVE mode when /usr/local/bin is not writable.
# See scripts/install-openshell.sh:394-425. Cover both paths.
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
# Resolve the actual install path so the framework's SandboxClient
# can spawn it without relying on PATH inheritance from the test
# process (the framework also accepts OPENSHELL_BIN as an override).
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx vitest run \
--project e2e-live \
test/e2e/live/gateway-guard-recovery.test.ts \
--reporter=default --reporter=test/e2e/risk-signal-reporter.ts --silent=false
- name: Upload artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
openclaw-inference-switch:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-inference-switch,') || contains(format(',{0},', inputs.targets), ',openclaw-inference-switch,') }}
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- mode: hosted
sandbox_name: e2e-openclaw-inference-switch
switch_provider: nvidia-prod
switch_model: nvidia/nemotron-3-super-120b-a12b
switch_inference_api: openai-completions
switch_mock_anthropic: "0"
- mode: anthropic
sandbox_name: e2e-openclaw-anthropic-inference-switch
switch_provider: compatible-anthropic-endpoint
switch_model: mock-anthropic-model
switch_inference_api: anthropic-messages
switch_mock_anthropic: "1"
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openclaw-inference-switch"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-inference-switch/${{ matrix.mode }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: "openclaw"
NEMOCLAW_E2E_SHARD: ${{ matrix.mode }}
NEMOCLAW_SANDBOX_NAME: ${{ matrix.sandbox_name }}
NEMOCLAW_SWITCH_PROVIDER: ${{ matrix.switch_provider }}
NEMOCLAW_SWITCH_MODEL: ${{ matrix.switch_model }}
NEMOCLAW_SWITCH_INFERENCE_API: ${{ matrix.switch_inference_api }}
NEMOCLAW_SWITCH_MOCK_ANTHROPIC: ${{ matrix.switch_mock_anthropic }}
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run OpenClaw inference switch live test
# Preserves
# the real install.sh/OpenShell/Docker/live-provider boundary while
# Vitest owns the route, config, registry, inference.local, and agent
# assertions.
env:
NVIDIA_API_KEY: ${{ matrix.mode == 'hosted' && secrets.NVIDIA_API_KEY || '' }}
NVIDIA_INFERENCE_API_KEY: ${{ matrix.mode == 'hosted' && secrets.NVIDIA_INFERENCE_API_KEY || '' }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-inference-switch.test.ts
- name: Upload OpenClaw inference switch artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-openclaw-inference-switch-${{ matrix.mode }}
path: e2e-artifacts/live/openclaw-inference-switch/${{ matrix.mode }}/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
# Bedrock Runtime compatible Anthropic endpoint contract.
bedrock-runtime-compatible-anthropic:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',bedrock-runtime-compatible-anthropic,') || contains(format(',{0},', inputs.targets), ',bedrock-runtime-compatible-anthropic,') }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
agent: [openclaw, hermes]
env:
E2E_JOB: "1"
E2E_TARGET_ID: "bedrock-runtime-compatible-anthropic"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/bedrock-runtime-compatible-anthropic/${{ matrix.agent }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_AGENT: ${{ matrix.agent }}
NEMOCLAW_E2E_SHARD: ${{ matrix.agent }}
NEMOCLAW_SANDBOX_NAME: e2e-bedrock-${{ matrix.agent }}
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Run Bedrock Runtime compatible Anthropic live test
# Direct Vitest coverage for
# Preserves the
# fake Bedrock Runtime endpoint, /etc/hosts mapping, source CLI
# onboard, OpenShell adapter route, agent-specific runtime probes, and
# leak-scan contract for both OpenClaw and Hermes.
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/bedrock-runtime-compatible-anthropic.test.ts
- name: Upload Bedrock Runtime compatible Anthropic artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-bedrock-runtime-compatible-anthropic-${{ matrix.agent }}
path: e2e-artifacts/live/bedrock-runtime-compatible-anthropic/${{ matrix.agent }}/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
gateway-health-honest:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',gateway-health-honest,') || contains(format(',{0},', inputs.targets), ',gateway-health-honest,') }}
runs-on: ubuntu-latest
timeout-minutes: 20
env:
E2E_JOB: "1"
E2E_TARGET_ID: "gateway-health-honest"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/gateway-health-honest
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run gateway health honesty live test
# It invokes
# real startGateway() with a sabotaged Docker-driver gateway binary
# and asserts the crashed process is never reported healthy.
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/gateway-health-honest.test.ts
- name: Upload gateway health honesty artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
device-auth-health:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',device-auth-health,') || contains(format(',{0},', inputs.targets), ',device-auth-health,') }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
E2E_JOB: "1"
E2E_TARGET_ID: "device-auth-health"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/device-auth-health
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-health-auth"
NEMOCLAW_DASHBOARD_PORT: "18789"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run device auth health live Vitest test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/device-auth-health.test.ts
- name: Upload device auth health artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
channels-add-remove:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',channels-add-remove,') || contains(format(',{0},', inputs.targets), ',channels-add-remove,') }}
runs-on: ubuntu-latest
timeout-minutes: 75
env:
E2E_JOB: "1"
E2E_TARGET_ID: "channels-add-remove"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/channels-add-remove
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-channels-add-remove"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
env:
NEMOCLAW_NON_INTERACTIVE: "1"
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run channels add/remove live test
# Preserves the
# real OpenClaw + Docker/OpenShell boundary for onboard-empty,
# channels add, rebuild, gateway credential reuse, policy-list, and
# channels remove cleanup; the test owns its authenticated local
# compatible-inference baseline.
env:
TELEGRAM_BOT_TOKEN: "test-fake-telegram-token-add-remove-e2e"
TELEGRAM_ALLOWED_IDS: "123456789"
TELEGRAM_REQUIRE_MENTION: "0"
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/channels-add-remove.test.ts
- name: Upload channels add/remove artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
telegram-injection:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',telegram-injection,') || contains(format(',{0},', inputs.targets), ',telegram-injection,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "telegram-injection"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/telegram-injection
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-telegram-injection"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
env:
NEMOCLAW_NON_INTERACTIVE: "1"
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run Telegram injection live test
# Preserves the
# real OpenShell sandbox boundary for shell metacharacter payloads,
# process-table leak checks, and validateName rejection probes.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/telegram-injection.test.ts
- name: Upload Telegram injection artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
channels-stop-start:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',channels-stop-start,') || contains(format(',{0},', inputs.targets), ',channels-stop-start,') }}
runs-on: ubuntu-latest
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
agent: [openclaw, hermes]
env:
E2E_JOB: "1"
E2E_TARGET_ID: "channels-stop-start"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/channels-stop-start/${{ matrix.agent }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_E2E_SHARD: ${{ matrix.agent }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_AGENT: ${{ matrix.agent }}
NEMOCLAW_CHANNELS_STOP_START_AGENT: ${{ matrix.agent }}
NEMOCLAW_SANDBOX_NAME: e2e-channels-stop-start-${{ matrix.agent }}
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell
env:
NEMOCLAW_NON_INTERACTIVE: "1"
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run channels stop/start live test
# Preserves the
# OpenClaw/Hermes messaging channel stop/start, rebuild, provider
# reuse, registry, policy-list, and in-sandbox config contracts.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
TELEGRAM_BOT_TOKEN: test-fake-telegram-token-stop-start-${{ matrix.agent }}
DISCORD_BOT_TOKEN: test-fake-discord-token-stop-start-${{ matrix.agent }}
SLACK_BOT_TOKEN: xoxb-fake-slack-token-stop-start-${{ matrix.agent }}
SLACK_APP_TOKEN: xapp-fake-slack-token-stop-start-${{ matrix.agent }}
WECHAT_BOT_TOKEN: test-fake-wechat-token-stop-start-${{ matrix.agent }}
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/channels-stop-start.test.ts
- name: Upload channels stop/start artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-channels-stop-start-${{ matrix.agent }}
path: e2e-artifacts/live/channels-stop-start/${{ matrix.agent }}/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
openclaw-slack-pairing:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-slack-pairing,') || contains(format(',{0},', inputs.targets), ',openclaw-slack-pairing,') }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openclaw-slack-pairing"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-slack-pairing
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-openclaw-slack-pairing"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run OpenClaw Slack pairing live test
# Preserves
# fake Slack Socket Mode/REST token rewrite and connect-shell approval.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
SLACK_BOT_TOKEN: xoxb-fake-slack-pairing-e2e
SLACK_APP_TOKEN: xapp-fake-slack-pairing-e2e
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-slack-pairing.test.ts
- name: Upload OpenClaw Slack pairing artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
issue-2478-crash-loop-recovery:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',issue-2478-crash-loop-recovery,') || contains(format(',{0},', inputs.targets), ',issue-2478-crash-loop-recovery,') }}
runs-on: ubuntu-latest
timeout-minutes: 30
env:
E2E_JOB: "1"
E2E_TARGET_ID: "issue-2478-crash-loop-recovery"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/issue-2478-crash-loop-recovery
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-2478"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: "Run issue #2478 crash-loop recovery live Vitest test"
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/issue-2478-crash-loop-recovery.test.ts
- name: "Upload issue #2478 crash-loop recovery artifacts"
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
openclaw-discord-pairing:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-discord-pairing,') || contains(format(',{0},', inputs.targets), ',openclaw-discord-pairing,') }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openclaw-discord-pairing"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-discord-pairing
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-openclaw-discord-pairing"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install OpenShell CLI
run: |
set -euo pipefail
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
- name: Run OpenClaw Discord pairing live test
# Preserves
# fake Discord Gateway token rewrite and connect-shell pairing approval.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
DISCORD_BOT_TOKEN: test-fake-discord-pairing-e2e
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
ls -la /usr/local/bin/openshell "$HOME/.local/bin/openshell" 2>&1 || true
exit 1
fi
export OPENSHELL_BIN
echo "Using OPENSHELL_BIN=$OPENSHELL_BIN"
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-discord-pairing.test.ts
- name: Upload OpenClaw Discord pairing artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
tunnel-lifecycle:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',tunnel-lifecycle,') || contains(format(',{0},', inputs.targets), ',tunnel-lifecycle,') }}
runs-on: ubuntu-latest
timeout-minutes: 75
env:
E2E_JOB: "1"
E2E_TARGET_ID: "tunnel-lifecycle"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/tunnel-lifecycle
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-tunnel-lifecycle"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
- name: Install and verify cloudflared prerequisite
env:
CLOUDFLARED_VERSION: "2026.6.1"
CLOUDFLARED_DEB_SHA256: "ccd02ec216c62bfa573395d8f72cb2e91e95cbdf8726a8acc06b3e2d9aa31526"
run: |
set -euo pipefail
if command -v cloudflared >/dev/null 2>&1; then
if cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}" >/dev/null; then
cloudflared --version
exit 0
fi
fi
cloudflared_deb="${RUNNER_TEMP}/cloudflared-${CLOUDFLARED_VERSION}-linux-amd64.deb"
curl -fL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb" -o "${cloudflared_deb}"
printf '%s %s\n' "${CLOUDFLARED_DEB_SHA256}" "${cloudflared_deb}" | sha256sum -c -
package="$(dpkg-deb -f "${cloudflared_deb}" Package)"
version="$(dpkg-deb -f "${cloudflared_deb}" Version)"
architecture="$(dpkg-deb -f "${cloudflared_deb}" Architecture)"
if [ "${package}" != "cloudflared" ] || [ "${version}" != "${CLOUDFLARED_VERSION}" ] || [ "${architecture}" != "amd64" ]; then
printf 'Unexpected cloudflared package metadata: package=%s version=%s architecture=%s\n' "${package}" "${version}" "${architecture}" >&2
exit 1
fi
sudo dpkg -i "${cloudflared_deb}"
cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}"
- name: Run tunnel lifecycle live test
# This preserves the
# real Docker/OpenShell onboard, host cloudflared quick-tunnel,
# local-dashboard readiness, public tunnel probe, and stop/status
# cleanup boundaries under Vitest.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/tunnel-lifecycle.test.ts
- name: Upload tunnel lifecycle artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
spark-install:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',spark-install,') || contains(format(',{0},', inputs.targets), ',spark-install,') }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
E2E_JOB: "1"
E2E_TARGET_ID: "spark-install"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/spark-install
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_FRESH: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-spark-install-ci"
NEMOCLAW_PROVIDER: "cloud"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@50281ee84c4a6fc759da95ea28fc0b7d9c378a28
with:
build-cli: "false"
- name: Run Spark install live test
# This preserves the
# Linux + Docker prerequisite gate, the plain non-interactive
# install.sh/public curl|bash boundary, install-log retention, PATH
# refresh, and post-install nemoclaw/openshell/--help checks.
env:
NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }}
run: |
set -euo pipefail
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/spark-install.test.ts
- name: Upload Spark install artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
# ── PR result comment ─────────────────────────────────────────────────────
# Posts a results table on the open PR for the dispatching branch (or the
# PR identified by `inputs.pr_number`). `if: always()` so the comment lands
# even when target jobs failed — that's the whole point of a result
# comment.
report-to-pr:
runs-on: ubuntu-latest
# This entire workflow is dispatch-only. Keeping selective jobs in `needs`
# makes the report wait for and record any requested job without adding
# skipped checks to the normal pull_request workflow.
needs: &e2e-result-jobs
[
generate-matrix,
live,
shared-e2e,
openshell-gateway-auth-contract,
mcp-bridge,
mcp-bridge-dev,
skill-agent,
openclaw-skill-cli,
inference-routing,
cloud-inference,
gpu-e2e,
agent-turn-latency,
kimi-inference-compat,
hermes-inference-switch,
brave-search,
ollama-auth-proxy,
cron-preflight-inference-local,
credential-sanitization,
credential-migration,
sessions-agents-cli,
hermes-e2e,
hermes-gpu-startup,
hermes-dashboard,
hermes-slack,
hermes-discord,
network-policy,
common-egress-agent,
shields-config,
hermes-shields-config,
rebuild-openclaw,
rebuild-hermes,
rebuild-hermes-stale-base,
sandbox-rebuild,
sandbox-rlimits-connect,
overlayfs-autofix,
state-backup-restore,
upgrade-stale-sandbox,
openshell-gateway-upgrade,
token-rotation,
messaging-compatible-endpoint,
messaging-providers,
bootstrap-install-smoke,
double-onboard,
jetson-nvmap-gpu,
concurrent-gateway-ports,
full-e2e,
security-posture,
cloud-onboard,
gpu-double-onboard,
onboard-repair,
issue-4462-scope-upgrade-approval,
onboard-resume,
model-router-provider-routed-inference,
sandbox-operations,
sandbox-survival,
diagnostics,
snapshot-commands,
openclaw-plugin-runtime-exdev,
openclaw-tui-chat-correlation,
gateway-guard-recovery,
issue-4434-tui-unreachable-inference,
openclaw-inference-switch,
bedrock-runtime-compatible-anthropic,
issue-2478-crash-loop-recovery,
gateway-health-honest,
device-auth-health,
channels-add-remove,
tunnel-lifecycle,
telegram-injection,
openclaw-discord-pairing,
openclaw-slack-pairing,
channels-stop-start,
spark-install,
]
if: ${{ always() && github.event_name == 'workflow_dispatch' && inputs.checkout_sha == '' }}
permissions:
actions: read
contents: read
# The issue-comment endpoint accepts pull request write permission for PR comments.
# Keep issues: write absent so this job cannot restore general issue routing.
pull-requests: write
steps:
- name: Check out the trusted E2E reporting helper
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.workflow_sha }}
persist-credentials: false
sparse-checkout: |
tools/e2e/report-e2e-results.mts
sparse-checkout-cone-mode: false
- name: Post E2E target results to PR
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
EXPLICIT_ONLY_JOBS: ${{ needs.generate-matrix.outputs.explicit_only_jobs }}
TEST_MATRIX: ${{ needs.generate-matrix.outputs.test_matrix }}
JOB_PR_NUMBER: ${{ inputs.pr_number }}
JOB_TARGETS: ${{ inputs.targets }}
JOBS: ${{ inputs.jobs }}
with:
script: |
const path = require('node:path');
const { pathToFileURL } = require('node:url');
const { resolveReportPr, loadReportJobs, renderE2eReport } = await import(
pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'tools/e2e/report-e2e-results.mts')).href
);
const needs = ${{ toJSON(needs) }};
const prNumber = await resolveReportPr({ github, context, core, env: process.env });
if (prNumber === undefined) return;
const { apiJobs, loaded } = await loadReportJobs({ github, context, core });
const report = renderE2eReport({
needs,
env: process.env,
apiJobs,
apiJobsLoaded: loaded,
context,
});
if (report.fatal) {
core.setFailed(report.fatal);
return;
}
for (const warning of report.warnings) core.warning(warning);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: report.body,
});
# ── Scheduled/manual scorecard ────────────────────────────────────────────
scorecard:
runs-on: ubuntu-latest
needs: *e2e-result-jobs
if: ${{ always() && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.checkout_sha == '')) }}
permissions:
actions: read
contents: read
steps:
- name: Checkout scorecard builders
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
sparse-checkout: |
ci/onboard-performance-budget.json
scripts/scorecard
sparse-checkout-cone-mode: false
- name: Generate E2E scorecard
id: scorecard
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
EXPLICIT_ONLY_JOBS: ${{ needs.generate-matrix.outputs.explicit_only_jobs }}
JOBS: ${{ inputs.jobs }}
TARGETS: ${{ inputs.targets }}
with:
script: |
const path = require('path');
const { buildScorecard } = require(
path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/coordinate-scorecard.mts'),
);
const scorecardJobs = require(
path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/summarize-jobs.mts'),
);
const traceTiming = require(
path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/analyze-trace-timing.mts'),
);
const needs = ${{ toJSON(needs) }};
// GitHub's jobs API is the canonical source because `needs.live`
// collapses every matrix target into one result and has no job URL.
// The typed helper owns and tests the degraded `needs` fallback.
const apiJobs = await scorecardJobs.loadWorkflowRunJobs({ github, context, core });
const trace = await traceTiming.buildTraceTimingResult({ github, context, core });
if (trace.budgetWarningMessage) core.warning(trace.budgetWarningMessage);
const { summaryMarkdown, scorecardData, slackData } = buildScorecard({
eventName: context.eventName,
actor: context.actor || '',
serverUrl: context.serverUrl,
repo: context.repo,
runId: context.runId,
rawJobs: process.env.JOBS || '',
rawTargets: process.env.TARGETS || '',
rawExplicitOnly: process.env.EXPLICIT_ONLY_JOBS || '',
needs,
apiJobs,
trace,
today: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
});
await core.summary.addRaw(summaryMarkdown).write();
core.setOutput('scorecardData', JSON.stringify(scorecardData));
core.setOutput('slackData', JSON.stringify(slackData));
- name: Post scorecard to Slack
# Webhook secrets never enter branch-dispatched runs. The payload is
# computed in the preceding no-secret step; this fixed publisher does
# not load code from the checked-out workflow ref.
if: ${{ steps.scorecard.outputs.slackData != '' && github.ref == 'refs/heads/main' }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
SLACK_WEBHOOK_URL_DAILY: ${{ secrets.SLACK_WEBHOOK_URL_DAILY }}
SLACK_WEBHOOK_URL_FULLRUN: ${{ secrets.SLACK_WEBHOOK_URL_FULLRUN }}
SLACK_WEBHOOK_URL_PREVIEW: ${{ secrets.SLACK_WEBHOOK_URL_PREVIEW }}
SLACK_DATA: ${{ steps.scorecard.outputs.slackData }}
POST_TO_SLACK: ${{ inputs.post_to_slack }}
with:
script: |
const data = JSON.parse(process.env.SLACK_DATA);
const envByChannel = {
daily: 'SLACK_WEBHOOK_URL_DAILY',
fullrun: 'SLACK_WEBHOOK_URL_FULLRUN',
preview: 'SLACK_WEBHOOK_URL_PREVIEW',
};
const channel = data?.channel;
const payload = data?.payload;
const attachments = payload?.attachments;
const validPayload =
Object.hasOwn(envByChannel, channel) &&
payload !== null &&
typeof payload === 'object' &&
typeof payload.text === 'string' &&
Array.isArray(attachments) &&
attachments.every(
(attachment) =>
attachment !== null &&
typeof attachment === 'object' &&
typeof attachment.color === 'string' &&
Array.isArray(attachment.blocks),
);
if (!validPayload) {
core.setFailed('Invalid precomputed Slack payload');
return;
}
if (channel === 'preview' && process.env.POST_TO_SLACK !== 'true') {
core.info('Selective dispatch without post_to_slack — skipping');
return;
}
const webhookUrl = process.env[envByChannel[channel]];
if (!webhookUrl) {
core.info(`Slack webhook for "${channel}" not configured — skipping`);
return;
}
const response = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data.payload),
});
if (!response.ok) {
core.setFailed(`Slack webhook returned ${response.status}`);
}