Skip to content

nightly-e2e

nightly-e2e #22

Workflow file for this run

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Nightly E2E tests:
#
# cloud-e2e Cloud inference (NVIDIA Endpoint API) on ubuntu-latest.
# cloud-experimental-e2e Experimental cloud inference test (main script skips embedded
# check-docs + final cleanup; follow-up steps run check-docs,
# skip/05-network-policy.sh, then cleanup.sh --verify with if: always()).
# messaging-providers-e2e Validates messaging credential provider/placeholder/L7-proxy chain
# for Telegram + Discord. Uses fake tokens. See PR #1081.
# token-rotation-e2e Validates that rotating a messaging token and re-running onboard
# propagates the new credential to the sandbox. Combined Telegram +
# Discord coverage with cross-talk assertions. See issue #1903.
# sandbox-survival-e2e Sandbox survival across gateway restarts (onboard, inference,
# gateway stop/start, verify sandbox + workspace + inference).
# hermes-e2e Hermes Agent E2E — install → onboard --agent hermes → health
# probe → live inference. Validates the multi-agent architecture.
# skip-permissions-e2e Validates --dangerously-skip-permissions activates the permissive
# policy (not stuck in Pending) and sandbox egress works (not 403).
# gpu-e2e Local Ollama inference on a GPU self-hosted runner.
# Controlled by the GPU_E2E_ENABLED repository variable.
# Set vars.GPU_E2E_ENABLED to "true" in repo settings to enable.
# notify-on-failure Auto-creates a GitHub issue when any E2E job fails.
#
# Runs directly on the runner (not inside Docker) because OpenShell bootstraps
# a K3s cluster inside a privileged Docker container — nesting would break networking.
#
# NVIDIA_API_KEY for cloud-e2e and cloud-experimental-e2e:
# - Repository secret: Settings → Secrets and variables → Actions → Repository secrets.
# - Environment secret: only available if the job sets `environment: <that environment name>`.
# (Storing the key under Environments / NVIDIA_API_KEY without `environment:` here leaves the
# variable empty in the job — repository secrets and environment secrets are separate.)
# Only runs on schedule and manual dispatch — never on PRs (secret protection).
name: nightly-e2e
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: nightly-e2e-${{ github.event_name }}-${{ github.event_name == 'workflow_dispatch' && github.ref || 'schedule' }}
cancel-in-progress: true
jobs:
cloud-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run cloud E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-nightly"
NEMOCLAW_RECREATE_SANDBOX: "1"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-full-e2e.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: install-log
path: /tmp/nemoclaw-e2e-install.log
if-no-files-found: ignore
cloud-experimental-e2e:
# DISABLED: CLI/docs command reference drift causes check-docs failures.
# Landlock fix landed in OpenShell#810 (v0.0.32+). Re-enable once
# docs-drift is resolved.
if: github.repository == 'NVIDIA/NemoClaw' && vars.CLOUD_EXPERIMENTAL_E2E_ENABLED == 'true'
runs-on: ubuntu-latest
# Main suite + check-docs + network-policy skip script can exceed 45m on cold runners.
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@v6
# Split Phase 5f (check-docs) and Phase 6 (cleanup) out of the main script so CI shows
# failures in dedicated steps; tear-down always runs last (if: always()).
- name: Run cloud-experimental E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
GITHUB_TOKEN: ${{ github.token }}
# Non-interactive install (expect-driven Phase 3 optional). Runner has no expect; Phase 5e TUI skips if expect is absent.
RUN_E2E_CLOUD_EXPERIMENTAL_INTERACTIVE_INSTALL: "0"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_POLICY_MODE: "custom"
NEMOCLAW_POLICY_PRESETS: "npm,pypi"
RUN_E2E_CLOUD_EXPERIMENTAL_SKIP_FINAL_CLEANUP: "1"
RUN_E2E_CLOUD_EXPERIMENTAL_SKIP_CHECK_DOCS: "1"
run: bash test/e2e/test-e2e-cloud-experimental.sh
- name: Documentation checks (check-docs.sh)
if: always()
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if [ -f "$HOME/.bashrc" ]; then
# shellcheck source=/dev/null
source "$HOME/.bashrc" 2>/dev/null || true
fi
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck source=/dev/null
. "$NVM_DIR/nvm.sh"
fi
if [ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi
bash test/e2e/e2e-cloud-experimental/check-docs.sh
- name: Network policy checks (skip/05-network-policy.sh)
if: always()
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
GITHUB_TOKEN: ${{ github.token }}
SANDBOX_NAME: e2e-cloud-experimental
NEMOCLAW_SANDBOX_NAME: e2e-cloud-experimental
run: |
set -euo pipefail
if [ -f "$HOME/.bashrc" ]; then
# shellcheck source=/dev/null
source "$HOME/.bashrc" 2>/dev/null || true
fi
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck source=/dev/null
. "$NVM_DIR/nvm.sh"
fi
if [ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi
bash test/e2e/e2e-cloud-experimental/skip/05-network-policy.sh
- name: Tear down cloud-experimental sandbox (always)
if: always()
env:
SANDBOX_NAME: e2e-cloud-experimental
NEMOCLAW_SANDBOX_NAME: e2e-cloud-experimental
run: |
set -euo pipefail
if [ -f "$HOME/.bashrc" ]; then
# shellcheck source=/dev/null
source "$HOME/.bashrc" 2>/dev/null || true
fi
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck source=/dev/null
. "$NVM_DIR/nvm.sh"
fi
if [ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi
bash test/e2e/e2e-cloud-experimental/cleanup.sh --verify
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: install-log-cloud-experimental
path: /tmp/nemoclaw-e2e-cloud-experimental-install.log
if-no-files-found: ignore
# ── Messaging Providers E2E ──────────────────────────────────
# Validates the full provider/placeholder/L7-proxy chain for messaging
# credentials (Telegram, Discord). Uses fake tokens by default — the L7
# proxy rewrites placeholders and the real API returns 401, proving the
# chain works. See: PR #1081
messaging-providers-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run messaging providers E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_POLICY_TIER: "open"
NEMOCLAW_SANDBOX_NAME: "e2e-msg-provider"
GITHUB_TOKEN: ${{ github.token }}
TELEGRAM_BOT_TOKEN: "test-fake-telegram-token-e2e"
DISCORD_BOT_TOKEN: "test-fake-discord-token-e2e"
run: bash test/e2e/test-messaging-providers.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: install-log-messaging-providers
path: /tmp/nemoclaw-e2e-install.log
if-no-files-found: ignore
# ── Token rotation (credential propagation to L7 proxy) ─────
# Validates that rotating a messaging token and re-running onboard
# propagates the new credential to the sandbox. Uses two fake tokens
# per provider (Telegram + Discord) to prove the sandbox is rebuilt on
# rotation and reused when unchanged.
# See: issue #1903
token-rotation-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run token rotation E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_POLICY_TIER: "open"
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: "test-fake-discord-A-rotation-e2e"
DISCORD_BOT_TOKEN_B: "test-fake-discord-B-rotation-e2e"
run: bash test/e2e/test-token-rotation.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: install-log-token-rotation
path: /tmp/nemoclaw-e2e-install.log
if-no-files-found: ignore
# ── Sandbox survival (gateway restart recovery) ──────────────
sandbox-survival-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run sandbox survival E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-survival"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-sandbox-survival.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: sandbox-survival-install-log
path: /tmp/nemoclaw-e2e-install.log
if-no-files-found: ignore
# ── Hermes Agent E2E ─────────────────────────────────────────
# Validates the multi-agent architecture by onboarding with --agent hermes,
# verifying the Hermes health probe, and running live inference through the
# Hermes sandbox. See: PR #1618
hermes-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run Hermes Agent E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-hermes"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_AGENT: "hermes"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-hermes-e2e.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: hermes-e2e-install-log
path: /tmp/nemoclaw-e2e-hermes-install.log
if-no-files-found: ignore
# ── Skip-permissions policy activation ────────────────────────
# Validates the --dangerously-skip-permissions onboard flag correctly
# activates the permissive network policy (not stuck in Pending).
# Reproduces the exact scenario from the bug report: onboard, verify
# policy is Active, curl from inside sandbox must not return 403.
skip-permissions-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run skip-permissions policy E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-skip-perms"
NEMOCLAW_DANGEROUSLY_SKIP_PERMISSIONS: "1"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-skip-permissions-policy.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: skip-permissions-install-log
path: /tmp/nemoclaw-e2e-install.log
if-no-files-found: ignore
# ── Sandbox operations (recovery + multi-sandbox isolation) ──
# Validates sandbox list, connect, status, logs, destroy, gateway
# auto-recovery after docker kill, registry rebuild, process recovery,
# multi-sandbox metadata, and cross-sandbox network isolation.
sandbox-operations-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run sandbox operations E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_POLICY_TIER: "open"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-sandbox-operations.sh
- name: Upload test log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: sandbox-operations-test-log
path: test-sandbox-operations-*.log
if-no-files-found: ignore
# ── Inference routing (credential isolation + error classification) ──
# TC-INF-05: real API key absent from sandbox env/process/filesystem
# TC-INF-06: invalid API key → classified credential error (PR-safe)
# TC-INF-07: unreachable endpoint → classified transport error (PR-safe)
inference-routing-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run inference error classification E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_POLICY_TIER: "open"
run: bash test/e2e/test-inference-routing.sh
- name: Upload test log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: inference-routing-test-log
path: test-inference-routing-*.log
if-no-files-found: ignore
# ── Network policy E2E ───────────────────────────────────────
# TC-NET-01..07, TC-NET-09: deny-by-default, whitelist, live policy-add,
# dry-run, hot-reload, inference exemption, permissive mode, SSRF validation.
network-policy-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run network policy E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_POLICY_TIER: "restricted"
NEMOCLAW_RECREATE_SANDBOX: "1"
run: bash test/e2e/test-network-policy.sh
- name: Upload test log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: network-policy-test-log
path: test-network-policy-*.log
if-no-files-found: ignore
# ── Deployment & Services E2E ────────────────────────────────
# TC-STATE-02: backup-workspace.sh lifecycle (backup → destroy → restore)
# TC-DEPLOY-01: nemoclaw start/stop (cloudflared tunnel)
# TC-DEPLOY-03: uninstall --keep-openshell (destructive, runs last in script)
deployment-services-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run deployment services E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-deploy-svc"
run: bash test/e2e/test-deployment-services.sh
- name: Upload test log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: deployment-services-test-log
path: test-deployment-services-*.log
if-no-files-found: ignore
# ── Diagnostics E2E ─────────────────────────────────────────
# TC-DIAG-04: nemoclaw --version, TC-DIAG-02: debug --quick,
# TC-DIAG-01: debug tarball + credential sanitization,
# TC-DIAG-05: sandbox config, TC-DIAG-03: credentials list
diagnostics-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run diagnostics E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_RECREATE_SANDBOX: "1"
run: bash test/e2e/test-diagnostics.sh
- name: Upload test log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: diagnostics-test-log
path: test-diagnostics-*.log
if-no-files-found: ignore
# ── Snapshot commands E2E ────────────────────────────────────
# Validates snapshot create/list/restore lifecycle: create a snapshot,
# list it, delete state, restore from snapshot, verify state recovered.
snapshot-commands-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run snapshot commands E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-snapshot"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-snapshot-commands.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: snapshot-commands-install-log
path: /tmp/nemoclaw-e2e-install.log
if-no-files-found: ignore
# ── Shields & config lifecycle E2E ───────────────────────────
# Validates shields down/up controls config mutability, config get/set/
# rotate-token, audit trail, and auto-restore timer.
shields-config-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run shields & config E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-shields"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-shields-config.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: shields-config-install-log
path: /tmp/nemoclaw-e2e-shields-install.log
if-no-files-found: ignore
# ── OpenClaw rebuild upgrade E2E ─────────────────────────────
# Reproduces NVBug 6076156: onboard with an older OpenClaw version,
# then rebuild to verify workspace state survives the upgrade.
rebuild-openclaw-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run OpenClaw rebuild upgrade E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-rebuild-oc"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-rebuild-openclaw.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: rebuild-openclaw-install-log
path: /tmp/nemoclaw-e2e-install.log
if-no-files-found: ignore
# ── Issue #1904: stale sandbox after NemoClaw upgrade ────────
# Exact reproduction of the reporter's scenario: install an older
# NemoClaw, create a sandbox, upgrade to current, verify the old
# sandbox is detected as stale and rebuilt with the new image.
upgrade-stale-sandbox-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run upgrade stale sandbox E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-upgrade-stale"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-upgrade-stale-sandbox.sh
- name: Upload install logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: upgrade-stale-sandbox-logs
path: |
/tmp/nemoclaw-e2e-old-install.log
/tmp/nemoclaw-e2e-upgrade-install.log
if-no-files-found: ignore
# ── Hermes rebuild upgrade E2E ──────────────────────────────
# Same upgrade scenario as OpenClaw but for Hermes Agent.
rebuild-hermes-e2e:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run Hermes rebuild upgrade E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-rebuild-hm"
NEMOCLAW_AGENT: "hermes"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-rebuild-hermes.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: rebuild-hermes-install-log
path: /tmp/nemoclaw-e2e-install.log
if-no-files-found: ignore
# ── GPU E2E (Ollama local inference) ──────────────────────────
# Enable by setting repository variable GPU_E2E_ENABLED=true
# (Settings → Secrets and variables → Actions → Variables)
#
# Runner labels: using 'self-hosted' for now. Refine to
# [self-hosted, linux, x64, gpu] once NVIDIA runner labels are confirmed.
gpu-e2e:
if: github.repository == 'NVIDIA/NemoClaw' && vars.GPU_E2E_ENABLED == 'true'
runs-on: self-hosted
timeout-minutes: 60
env:
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-gpu-ollama"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_PROVIDER: "ollama"
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Verify GPU availability
run: |
echo "=== GPU Info ==="
nvidia-smi
echo ""
echo "=== VRAM ==="
nvidia-smi --query-gpu=name,memory.total --format=csv,noheader
echo ""
echo "=== Docker ==="
docker info --format '{{.ServerVersion}}'
- name: Run GPU E2E test (Ollama local inference)
run: bash test/e2e/test-gpu-e2e.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: gpu-e2e-install-log
path: /tmp/nemoclaw-gpu-e2e-install.log
if-no-files-found: ignore
- name: Upload test log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: gpu-e2e-test-log
path: /tmp/nemoclaw-gpu-e2e-test.log
if-no-files-found: ignore
notify-on-failure:
runs-on: ubuntu-latest
needs:
[
cloud-e2e,
cloud-experimental-e2e,
messaging-providers-e2e,
token-rotation-e2e,
sandbox-survival-e2e,
hermes-e2e,
skip-permissions-e2e,
sandbox-operations-e2e,
inference-routing-e2e,
network-policy-e2e,
deployment-services-e2e,
diagnostics-e2e,
snapshot-commands-e2e,
shields-config-e2e,
rebuild-openclaw-e2e,
upgrade-stale-sandbox-e2e,
rebuild-hermes-e2e,
gpu-e2e,
]
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
permissions:
issues: write
steps:
- name: Create or update failure issue
uses: actions/github-script@v7
with:
script: |
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const title = 'Nightly E2E failed';
const needs = ${{ toJSON(needs) }};
const failed = Object.entries(needs).filter(([, v]) => v.result === 'failure').map(([k]) => k);
const cancelled = Object.entries(needs).filter(([, v]) => v.result === 'cancelled').map(([k]) => k);
const summary = [
failed.length ? `**Failed:** ${failed.join(', ')}` : '',
cancelled.length ? `**Cancelled:** ${cancelled.join(', ')}` : '',
].filter(Boolean).join('\n');
const { data: existing } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'CI/CD',
per_page: 100,
});
const match = existing.find(i => !i.pull_request && i.title.startsWith(title));
if (match) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: match.number,
body: `Failed again on ${new Date().toISOString().split('T')[0]}.\n\n**Run:** ${runUrl}\n${summary}\n**Artifacts:** Check the run artifacts for install/test logs (artifact names vary by job).`,
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `${title} — ${new Date().toISOString().split('T')[0]}`,
body: `The nightly E2E pipeline failed.\n\n**Run:** ${runUrl}\n${summary}\n**Artifacts:** Check the run artifacts for install/test logs (artifact names vary by job).`,
labels: ['bug', 'CI/CD'],
});
}