Skip to content

E2E main

E2E main #15

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) || inputs.correlation_id != '' && format('E2E {0} ({1})', github.ref_name, 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, jetson-nvmap-gpu, and staging-brev-launchable are skipped unless selected."
required: false
default: ""
type: string
include_staging_brev_launchable:
description: "Include Exact staging Brev Launchable in a full E2E run when jobs and targets are empty."
required: false
default: false
type: boolean
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
checkout_repository:
description: PR head repository selected by the controller.
required: false
default: ""
type: string
controller_check_id:
description: Controller-owned check run that authorizes this exact E2E run.
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:
checks: read
contents: read
pull-requests: read
concurrency:
group: e2e-${{ github.ref }}-${{ inputs.checkout_sha != '' && format('pr-{0}', inputs.pr_number) || (inputs.include_staging_brev_launchable && inputs.jobs == '' && inputs.targets == '' && format('full-{0}', github.run_id)) || 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:
base-image-publication:
runs-on: ubuntu-latest
timeout-minutes: 55
permissions:
actions: read
contents: read
steps:
- id: publication_mode
name: Classify base-image publication requirement
env:
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
EVENT_NAME: ${{ github.event_name }}
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
shell: bash
run: |
set -euo pipefail
case "${REPOSITORY}:${REF}:${EVENT_NAME}:${CHECKOUT_SHA:+controller}" in
NVIDIA/NemoClaw:refs/heads/main:schedule:|NVIDIA/NemoClaw:refs/heads/main:workflow_dispatch:)
required=1
;;
NVIDIA/NemoClaw:refs/heads/main:workflow_dispatch:controller)
required=0
;;
*)
echo "::error::base-image publication mode is not trusted" >&2
exit 1
;;
esac
printf 'required=%s\n' "${required}" >> "${GITHUB_OUTPUT}"
- name: Check out trusted E2E workflow
if: ${{ steps.publication_mode.outputs.required == '1' }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Set up Node for publication verification
if: ${{ steps.publication_mode.outputs.required == '1' }}
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
- name: Verify applicable base-image publication
if: ${{ steps.publication_mode.outputs.required == '1' }}
env:
EXPECTED_SHA: ${{ github.sha }}
GITHUB_TOKEN: ${{ github.token }}
run: node --experimental-strip-types --no-warnings tools/e2e/base-image-publication.mts --wait-seconds 3000 --poll-seconds 30
generate-matrix:
needs: base-image-publication
runs-on: ubuntu-latest
timeout-minutes: 10
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 }}
runner_routing: ${{ steps.runner_routing.outputs.runner_routing }}
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"}]'
;;
ubuntu-repo-docker-post-reboot-recovery)
matrix='[{"id":"ubuntu-repo-docker-post-reboot-recovery","runner":"ubuntu-latest","label":"ubuntu-repo-docker-post-reboot-recovery"}]'
;;
ubuntu-repo-cloud-langchain-deepagents-code,ubuntu-repo-docker-post-reboot-recovery)
matrix='[{"id":"ubuntu-repo-cloud-langchain-deepagents-code","runner":"ubuntu-latest","label":"ubuntu-repo-cloud-langchain-deepagents-code"},{"id":"ubuntu-repo-docker-post-reboot-recovery","runner":"ubuntu-latest","label":"ubuntu-repo-docker-post-reboot-recovery"}]'
;;
*)
echo "::error::PR E2E target is not approved by the trusted controller" >&2
exit 1
;;
esac
printf 'matrix=%s\n' "${matrix}" >> "${GITHUB_OUTPUT}"
- id: runner_routing
name: Build trusted larger-runner routing
env:
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
LARGER_RUNNER_LABEL: ${{ vars.E2E_LARGER_RUNNER_LABEL }}
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
shell: bash
run: |
set -euo pipefail
larger_runner="ubuntu-latest"
if [[ "${REPOSITORY}" == "NVIDIA/NemoClaw" && "${REF}" == "refs/heads/main" && -z "${CHECKOUT_SHA}" && -n "${LARGER_RUNNER_LABEL}" ]]; then
if [[ ! "${LARGER_RUNNER_LABEL}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ ]]; then
echo "::error::E2E_LARGER_RUNNER_LABEL must be a 1-64 character workflow label using letters, digits, dots, underscores, or hyphens" >&2
exit 1
fi
larger_runner="${LARGER_RUNNER_LABEL}"
fi
runner_routing="$(jq -cn --arg standard "ubuntu-latest" --arg larger "${larger_runner}" '{"channels-stop-start-hermes":$larger,"channels-stop-start-openclaw":$standard,"common-egress-agent":$larger,"hermes-discord":$larger,"hermes-e2e":$larger,"hermes-inference-switch":$larger,"hermes-shields-config":$larger,"mcp-bridge-deepagents":$larger,"mcp-bridge-hermes":$larger,"mcp-bridge-openclaw":$standard,"rebuild-hermes":$larger,"rebuild-hermes-stale-base":$larger,"security-posture-hermes":$larger,"security-posture-openclaw":$standard}')"
printf 'runner_routing=%s\n' "${runner_routing}" >> "${GITHUB_OUTPUT}"
- name: Authenticate controller dispatch
if: ${{ inputs.checkout_sha != '' }}
env:
ACTOR: ${{ github.actor }}
BASE_SHA: ${{ inputs.base_sha }}
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
CONTROLLER_CHECK_ID: ${{ inputs.controller_check_id }}
CORRELATION_ID: ${{ inputs.correlation_id }}
GITHUB_TOKEN: ${{ github.token }}
JOBS: ${{ inputs.jobs }}
PLAN_HASH: ${{ inputs.plan_hash }}
PR_NUMBER: ${{ inputs.pr_number }}
RUN_ATTEMPT: ${{ github.run_attempt }}
RUN_ID: ${{ github.run_id }}
TARGETS: ${{ inputs.targets }}
shell: bash
run: |
set -euo pipefail
[[ "$ACTOR" == "github-actions[bot]" ]] || { echo "::error::PR E2E must be dispatched by the trusted controller"; exit 1; }
[[ "$RUN_ATTEMPT" == "1" ]] || { echo "::error::controller-authorized PR E2E cannot be rerun"; exit 1; }
[[ "$CONTROLLER_CHECK_ID" =~ ^[1-9][0-9]*$ ]] || { echo "::error::controller_check_id must be a positive integer"; exit 1; }
expected_external_id="nemoclaw-pr-e2e:v2:${PR_NUMBER}:${CHECKOUT_SHA}:${BASE_SHA}"
expected_run_url="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}"
jobs_summary="${JOBS//,/, }"
targets_summary="${TARGETS//,/, }"
expected_summary="Risk plan ${PLAN_HASH} selected jobs: ${jobs_summary:-none}; targets: ${targets_summary:-none}. Child run: ${expected_run_url}."
controller_auth_max_attempts=45
controller_auth_poll_seconds=2
check_json=""
for ((_attempt = 1; _attempt <= controller_auth_max_attempts; _attempt++)); do
if check_json="$(curl --fail --silent --show-error --proto '=https' \
--header "Authorization: Bearer ${GITHUB_TOKEN}" \
--header "Accept: application/vnd.github+json" \
--header "Cache-Control: no-cache" \
--header "Pragma: no-cache" \
--header "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/check-runs/${CONTROLLER_CHECK_ID}")" &&
[[ "$(jq -r '.output.summary // ""' <<< "$check_json")" == "$expected_summary" ]]; then
break
fi
check_json=""
if (( _attempt < controller_auth_max_attempts )); then
sleep "$controller_auth_poll_seconds"
fi
done
[[ -n "$check_json" ]] || { echo "::error::trusted controller authorization was not published for this run"; exit 1; }
jq -e \
--argjson check_id "$CONTROLLER_CHECK_ID" \
--arg external_id "$expected_external_id" \
--arg head_sha "$CHECKOUT_SHA" \
--arg summary "$expected_summary" \
'
.id == $check_id and
.name == "E2E / PR Gate Coordination" and
.app.id == 15368 and
.app.slug == "github-actions" and
.head_sha == $head_sha and
.external_id == $external_id and
.status == "in_progress" and
.conclusion == null and
.output.summary == $summary
' <<< "$check_json" >/dev/null ||
{ echo "::error::controller check does not authorize this exact E2E run"; exit 1; }
- name: Authorize Launchable E2E maintainer dispatch
if: ${{ github.event_name == 'workflow_dispatch' && ((inputs.jobs == 'staging-brev-launchable' && inputs.targets == '') || (inputs.include_staging_brev_launchable && inputs.jobs == '' && inputs.targets == '')) }}
env:
ACTOR: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
TRIGGERING_ACTOR: ${{ github.triggering_actor }}
shell: bash
run: |
set -euo pipefail
require_maintainer() {
local maintainer="$1"
if [[ ! "$maintainer" =~ ^[A-Za-z0-9-]{1,39}$ || "$maintainer" == -* || "$maintainer" == *- ]]; then
echo "::error::Launchable E2E actor is invalid" >&2
exit 1
fi
local permission_json
permission_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}/collaborators/${maintainer}/permission")"
if [[ "$(jq -r '.user.login // ""' <<< "$permission_json" | tr '[:upper:]' '[:lower:]')" != "$(tr '[:upper:]' '[:lower:]' <<< "$maintainer")" ]]; then
echo "::error::Launchable E2E permission response did not match the actor" >&2
exit 1
fi
case "$(jq -r '.role_name // ""' <<< "$permission_json")" in
maintain | admin) ;;
*)
echo "::error::Launchable E2E requires a repository maintainer or administrator" >&2
exit 1
;;
esac
}
require_maintainer "$ACTOR"
if [[ "${TRIGGERING_ACTOR,,}" != "${ACTOR,,}" ]]; then
require_maintainer "$TRIGGERING_ACTOR"
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
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_REPOSITORY: ${{ inputs.checkout_repository }}
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_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "::error::checkout_repository must be an owner/repository name"; 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; }
case "$TARGETS" in
""|ubuntu-repo-cloud-langchain-deepagents-code|ubuntu-repo-docker-post-reboot-recovery|ubuntu-repo-cloud-langchain-deepagents-code,ubuntu-repo-docker-post-reboot-recovery) ;;
*) echo "::error::PR E2E target is not approved by the trusted controller"; exit 1 ;;
esac
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")" == "$CHECKOUT_REPOSITORY" ]] || { echo "::error::checkout_repository must match the PR head 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@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
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
- name: Record trusted E2E dispatch receipt
if: ${{ github.event_name == 'workflow_dispatch' }}
env:
ALLOW_JETSON_RUNNER_QUEUE: ${{ inputs.allow_jetson_runner_queue && 'true' || 'false' }}
CANDIDATE_SHA: ${{ inputs.checkout_sha || github.sha }}
DISPATCH_JOBS: ${{ inputs.jobs }}
DISPATCH_RECEIPT_DIR: ${{ runner.temp }}/nemoclaw-e2e-dispatch
DISPATCH_TARGETS: ${{ inputs.targets }}
EVENT_NAME: ${{ github.event_name }}
INCLUDE_STAGING_BREV_LAUNCHABLE: ${{ inputs.include_staging_brev_launchable && 'true' || 'false' }}
RUN_ATTEMPT: ${{ github.run_attempt }}
RUN_ID: ${{ github.run_id }}
run: |
set -euo pipefail
install -d -m 0700 "$DISPATCH_RECEIPT_DIR"
jq -n \
--arg candidateSha "$CANDIDATE_SHA" \
--arg eventName "$EVENT_NAME" \
--arg jobs "$DISPATCH_JOBS" \
--arg targets "$DISPATCH_TARGETS" \
--arg workflowRunId "$RUN_ID" \
--argjson allowJetsonRunnerQueue "$ALLOW_JETSON_RUNNER_QUEUE" \
--argjson includeStagingBrevLaunchable "$INCLUDE_STAGING_BREV_LAUNCHABLE" \
--argjson workflowRunAttempt "$RUN_ATTEMPT" \
'{
kind: "nemoclaw-e2e-dispatch-v1",
candidateSha: $candidateSha,
eventName: $eventName,
workflowRunId: $workflowRunId,
workflowRunAttempt: $workflowRunAttempt,
jobs: $jobs,
targets: $targets,
allowJetsonRunnerQueue: $allowJetsonRunnerQueue,
includeStagingBrevLaunchable: $includeStagingBrevLaunchable,
defaultSuiteSelected: ($jobs == "" and $targets == "")
}' >"$DISPATCH_RECEIPT_DIR/dispatch.json"
- name: Upload trusted E2E dispatch receipt
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-dispatch-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/nemoclaw-e2e-dispatch/dispatch.json
retired-selector-compatibility:
needs: generate-matrix
if: ${{ inputs.checkout_sha != '' && (contains(format(',{0},', inputs.jobs), ',credential-migration,') || contains(format(',{0},', inputs.jobs), ',credential-sanitization,') || contains(format(',{0},', inputs.jobs), ',diagnostics,') || contains(format(',{0},', inputs.jobs), ',docs-validation,') || contains(format(',{0},', inputs.jobs), ',gateway-drift-preflight,') || contains(format(',{0},', inputs.jobs), ',gateway-health-honest,') || contains(format(',{0},', inputs.jobs), ',onboard-negative-paths,') || contains(format(',{0},', inputs.jobs), ',openshell-version-pin,') || contains(format(',{0},', inputs.jobs), ',sandbox-rebuild,') || contains(format(',{0},', inputs.jobs), ',ubuntu-repo-cli-smoke,') || contains(format(',{0},', inputs.jobs), ',upgrade-stale-sandbox,') || contains(format(',{0},', inputs.targets), ',sandbox-rebuild,') || contains(format(',{0},', inputs.targets), ',upgrade-stale-sandbox,')) }}
runs-on: ubuntu-latest
timeout-minutes: 15
env:
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/retired-selector-compatibility
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Verify retired selector replacements
env:
JOBS: ${{ inputs.jobs }}
TARGETS: ${{ inputs.targets }}
run: npx tsx tools/e2e/retired-selector-compatibility.mts
- name: Upload retired selector compatibility evidence
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-retired-selector-compatibility
path: e2e-artifacts/live/retired-selector-compatibility/
staging-brev-launchable:
name: Exact staging Brev Launchable
needs: generate-matrix
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && ((inputs.jobs == 'staging-brev-launchable' && inputs.targets == '') || (inputs.include_staging_brev_launchable && inputs.jobs == '' && inputs.targets == '')) }}
runs-on: ubuntu-latest
timeout-minutes: 180
permissions:
contents: read
concurrency:
group: staging-brev-launchable-cpu
queue: max
cancel-in-progress: false
env:
CANDIDATE_SHA: ${{ inputs.checkout_sha || github.sha }}
E2E_DEFAULT_ENABLED: "0"
E2E_JOB: "1"
INSTANCE_NAME: nclaw-e2e-${{ github.run_id }}-${{ github.run_attempt }}
steps:
- name: Checkout trusted Launchable lane
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.workflow_sha }}
persist-credentials: false
sparse-checkout: |
tools/e2e/brev-launchable-e2e.sh
sparse-checkout-cone-mode: false
- id: workspace
name: Prepare the trusted lane
env:
BREV_API_KEY: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && secrets.BREV_API_KEY || '' }}
BREV_CLI_SHA256: 5a6e70374db9be33f85f299161733b4a8409840d47638c781429b96e8d53704f
BREV_CLI_VERSION: 0.6.330
BREV_ORG_ID: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && secrets.BREV_ORG_ID || '' }}
run: |
set -euo pipefail
work_dir="$(mktemp -d "${RUNNER_TEMP}/nemoclaw-launchable-e2e.XXXXXX")"
chmod 700 "$work_dir"
archive="${RUNNER_TEMP}/brev-cli.tar.gz"
curl -fsSL -o "$archive" "https://github.com/brevdev/brev-cli/releases/download/v${BREV_CLI_VERSION}/brev-cli_${BREV_CLI_VERSION}_linux_amd64.tar.gz"
printf '%s %s\n' "$BREV_CLI_SHA256" "$archive" | sha256sum -c -
tar -xzf "$archive" -C "${RUNNER_TEMP}" brev && sudo install -m 0755 "${RUNNER_TEMP}/brev" /usr/local/bin/brev
brev login --api-key "$BREV_API_KEY" --org-id "$BREV_ORG_ID"
printf 'work_dir=%s\n' "$work_dir" >> "$GITHUB_OUTPUT"
- name: Record E2E dispatch identity
env:
CANDIDATE_SHA: ${{ env.CANDIDATE_SHA }}
DISPATCH_JOBS: ${{ inputs.jobs }}
DISPATCH_TARGETS: ${{ inputs.targets }}
EVENT_NAME: ${{ github.event_name }}
INCLUDE_STAGING_BREV_LAUNCHABLE: ${{ inputs.include_staging_brev_launchable && 'true' || 'false' }}
RUN_ATTEMPT: ${{ github.run_attempt }}
RUN_ID: ${{ github.run_id }}
WORK_DIR: ${{ steps.workspace.outputs.work_dir }}
run: |
jq -n \
--arg candidateSha "$CANDIDATE_SHA" \
--arg eventName "$EVENT_NAME" \
--arg jobs "$DISPATCH_JOBS" \
--arg targets "$DISPATCH_TARGETS" \
--arg workflowRunId "$RUN_ID" \
--argjson includeStagingBrevLaunchable "$INCLUDE_STAGING_BREV_LAUNCHABLE" \
--argjson workflowRunAttempt "$RUN_ATTEMPT" \
'{
kind: "nemoclaw-e2e-dispatch-v1",
candidateSha: $candidateSha,
eventName: $eventName,
workflowRunId: $workflowRunId,
workflowRunAttempt: $workflowRunAttempt,
jobs: $jobs,
targets: $targets,
includeStagingBrevLaunchable: $includeStagingBrevLaunchable,
defaultSuiteSelected: ($jobs == "" and $targets == "")
}' >"$WORK_DIR/dispatch.json"
- name: Build, deploy, verify, test, and clean up
env:
BREV_LAUNCHABLE_ID: ${{ vars.NEMOCLAW_STAGING_LAUNCHABLE_ID }}
GH_TOKEN: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && secrets.NEMOCLAW_IMAGE_DISPATCH_TOKEN || '' }}
NVIDIA_INFERENCE_API_KEY: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && secrets.NVIDIA_INFERENCE_API_KEY || '' }}
WORK_DIR: ${{ steps.workspace.outputs.work_dir }}
run: tools/e2e/brev-launchable-e2e.sh
- name: Upload Launchable evidence
if: ${{ always() && steps.workspace.outputs.work_dir != '' }}
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: staging-brev-launchable-${{ env.CANDIDATE_SHA }}-${{ github.run_id }}
path: |
${{ steps.workspace.outputs.work_dir }}/lane.log
${{ steps.workspace.outputs.work_dir }}/dispatch.json
${{ steps.workspace.outputs.work_dir }}/launchable-e2e.json
${{ steps.workspace.outputs.work_dir }}/full-e2e.log
${{ steps.workspace.outputs.work_dir }}/cleanup.json
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
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@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
# 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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
- id: artifact_safety
name: Validate final OpenShell gateway auth contract artifacts
if: always()
run: node --experimental-strip-types --no-warnings tools/e2e/openshell-gateway-auth-artifact-safety.mts "$E2E_ARTIFACT_DIR"
- name: Upload OpenShell gateway auth contract artifacts
if: ${{ always() && steps.artifact_safety.outcome == 'success' && steps.artifact_safety.outputs.approved_path != '' }}
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-openshell-gateway-auth-contract
path: ${{ steps.artifact_safety.outputs.approved_path }}
- 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: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)[format('mcp-bridge-{0}', matrix.agent)] }}
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:
- id: trusted_hermes_swap
name: Provision trusted Hermes E2E swap
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.agent == 'hermes' }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
DISPATCH_SHA: ${{ github.sha }}
ENV: /dev/null
EVENT_NAME: ${{ github.event_name }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
LC_ALL: C
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
RUNNER_ARCH_KIND: ${{ runner.arch }}
RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }}
RUNNER_OS_KIND: ${{ runner.os }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: &trusted-hermes-e2e-swap |
set -euo pipefail
readonly swap_dir="/mnt/nemoclaw-hermes-e2e-swap"
readonly required_swap_bytes=34359738368
readonly swap_file_bytes=34359742464
readonly reserve_bytes=17179869184
readonly activation_observation_attempts=5
readonly activation_observation_delay_seconds=1
swap_file=""
swap_activation_succeeded=0
fail() {
printf 'Trusted Hermes E2E swap setup failed: %s\n' "$1" >&2
exit 1
}
if [[ "${REPOSITORY}" != "NVIDIA/NemoClaw" || "${REF}" != "refs/heads/main" ]]; then
fail "workflow must run from NVIDIA/NemoClaw main"
fi
if [[ "${EVENT_NAME}" != "schedule" && "${EVENT_NAME}" != "workflow_dispatch" ]]; then
fail "workflow event must be schedule or workflow_dispatch"
fi
# PR E2E mode: controller-dispatched PR commit.
if [[ "${EVENT_NAME}" == "workflow_dispatch" && -n "${CHECKOUT_SHA}" ]]; then
if [[ ! "${CHECKOUT_SHA}" =~ ^[0-9a-f]{40}$ ]]; then
fail "checkout SHA must be lowercase 40-hex"
fi
if [[ ! "${EXPECTED_WORKFLOW_SHA}" =~ ^[0-9a-f]{40}$ || "${WORKFLOW_SHA}" != "${EXPECTED_WORKFLOW_SHA}" || "${WORKFLOW_SHA}" != "${DISPATCH_SHA}" ]]; then
fail "workflow source must match the trusted dispatch revision"
fi
else
# Direct-main mode: schedule or manual trigger on main.
if [[ -n "${CHECKOUT_SHA}" || -n "${EXPECTED_WORKFLOW_SHA}" ]]; then
fail "direct main runs must not request an alternate checkout or workflow revision"
fi
if [[ ! "${WORKFLOW_SHA}" =~ ^[0-9a-f]{40}$ || "${WORKFLOW_SHA}" != "${DISPATCH_SHA}" ]]; then
fail "direct main workflow source must match the run revision"
fi
fi
if [[ "${RUNNER_ENVIRONMENT_KIND}" != "github-hosted" || "${RUNNER_OS_KIND}" != "Linux" || "${RUNNER_ARCH_KIND}" != "X64" ]]; then
fail "swap fallback requires an ephemeral GitHub-hosted Linux x64 runner"
fi
mnt_metadata="$(/usr/bin/stat -c "%F:%u:%g" -- /mnt)"
if [[ "${mnt_metadata}" != "directory:0:0" ]]; then
fail "/mnt must be a root-owned directory"
fi
read_active_swap_bytes() {
/usr/bin/sudo -n /usr/sbin/swapon --show=SIZE --bytes --noheadings |
/usr/bin/awk '{ total += $1 } END { printf "%.0f", total }'
}
active_swap_bytes="$(read_active_swap_bytes)"
active_swap_bytes="${active_swap_bytes:-0}"
if [[ ! "${active_swap_bytes}" =~ ^[0-9]+$ ]]; then
fail "unable to determine active swap capacity"
fi
if (( active_swap_bytes >= required_swap_bytes )); then
printf 'Hermes E2E swap is already sufficient: %s bytes active\n' "${active_swap_bytes}"
exit 0
fi
available_bytes="$(/usr/bin/df --block-size=1 --output=avail /mnt | /usr/bin/tail -n 1 | /usr/bin/tr -d "[:space:]")"
if [[ ! "${available_bytes}" =~ ^[0-9]+$ ]]; then
fail "unable to determine available disk capacity under /mnt"
fi
required_disk_bytes=$((swap_file_bytes + reserve_bytes))
if (( available_bytes < required_disk_bytes )); then
fail "insufficient disk capacity: ${available_bytes} bytes available, ${required_disk_bytes} required"
fi
if /usr/bin/sudo -n /usr/bin/test -e "${swap_dir}" || /usr/bin/sudo -n /usr/bin/test -L "${swap_dir}"; then
fail "refusing unexpected pre-existing swap path"
fi
directory_created=0
cleanup_partial_swap() {
status="$?"
if (( status != 0 && directory_created == 1 )); then
if active_swap_names="$(/usr/bin/sudo -n /usr/sbin/swapon --show=NAME --noheadings --raw 2>/dev/null)"; then
fixed_swap_active=0
while IFS= read -r active_swap_name; do
if [[ -n "${swap_file}" && "${active_swap_name}" == "${swap_file}" ]]; then
fixed_swap_active=1
break
fi
done <<< "${active_swap_names}"
if (( fixed_swap_active == 1 || swap_activation_succeeded == 1 )); then
if /usr/bin/sudo -n /usr/sbin/swapoff "${swap_file}" 2>/dev/null; then
/usr/bin/sudo -n /usr/bin/rm -f -- "${swap_file}" || true
/usr/bin/sudo -n /usr/bin/rmdir -- "${swap_dir}" || true
else
printf 'Preserving active Hermes E2E swap after setup failure: %s\n' "${swap_file}" >&2
fi
else
if [[ -n "${swap_file}" ]]; then
/usr/bin/sudo -n /usr/bin/rm -f -- "${swap_file}" || true
fi
/usr/bin/sudo -n /usr/bin/rmdir -- "${swap_dir}" || true
fi
else
printf 'Preserving Hermes E2E swap because active swap could not be queried: %s\n' "${swap_file}" >&2
fi
fi
trap - EXIT
exit "${status}"
}
trap cleanup_partial_swap EXIT
/usr/bin/sudo -n /usr/bin/mkdir -m 0700 -- "${swap_dir}"
directory_created=1
directory_metadata="$(/usr/bin/sudo -n /usr/bin/stat -c "%F:%u:%g:%a" -- "${swap_dir}")"
if [[ "${directory_metadata}" != "directory:0:0:700" ]]; then
fail "swap directory must be a root-owned mode-0700 directory"
fi
swap_file="$(/usr/bin/sudo -n /usr/bin/mktemp --tmpdir="${swap_dir}" nemoclaw-hermes.XXXXXXXX.swap)"
if ! /usr/bin/sudo -n /usr/bin/test -f "${swap_file}" || /usr/bin/sudo -n /usr/bin/test -L "${swap_file}"; then
fail "swap file must be a regular non-symlink"
fi
file_metadata="$(/usr/bin/sudo -n /usr/bin/stat -c "%u:%g:%a" -- "${swap_file}")"
if [[ "${file_metadata}" != "0:0:600" ]]; then
fail "swap file must be root-owned mode 0600"
fi
/usr/bin/sudo -n /usr/bin/fallocate -l "${swap_file_bytes}" "${swap_file}"
file_size_bytes="$(/usr/bin/sudo -n /usr/bin/stat -c "%s" -- "${swap_file}")"
if [[ ! "${file_size_bytes}" =~ ^[0-9]+$ || "${file_size_bytes}" -ne "${swap_file_bytes}" ]]; then
fail "swap file size does not match the fixed backing allocation"
fi
remaining_bytes="$(/usr/bin/df --block-size=1 --output=avail /mnt | /usr/bin/tail -n 1 | /usr/bin/tr -d "[:space:]")"
if [[ ! "${remaining_bytes}" =~ ^[0-9]+$ || "${remaining_bytes}" -lt "${reserve_bytes}" ]]; then
fail "swap allocation did not preserve the required disk reserve"
fi
/usr/bin/sudo -n /usr/sbin/mkswap --quiet "${swap_file}"
/usr/bin/sudo -n /usr/sbin/swapon "${swap_file}"
swap_activation_succeeded=1
observe_provisioned_swap() {
activation_observation_attempt=1
while (( activation_observation_attempt <= activation_observation_attempts )); do
provisioned_swap_active=0
if active_swap_names="$(/usr/bin/sudo -n /usr/sbin/swapon --show=NAME --noheadings --raw 2>/dev/null)"; then
while IFS= read -r active_swap_name; do
if [[ "${active_swap_name}" == "${swap_file}" ]]; then
provisioned_swap_active=1
break
fi
done <<< "${active_swap_names}"
fi
if observed_swap_bytes="$(read_active_swap_bytes 2>/dev/null)"; then
observed_swap_bytes="${observed_swap_bytes:-0}"
if [[ "${observed_swap_bytes}" =~ ^[0-9]+$ ]] &&
(( provisioned_swap_active == 1 && observed_swap_bytes >= required_swap_bytes )); then
active_swap_bytes="${observed_swap_bytes}"
return 0
fi
fi
if (( activation_observation_attempt < activation_observation_attempts )); then
/usr/bin/sleep "${activation_observation_delay_seconds}"
fi
activation_observation_attempt=$((activation_observation_attempt + 1))
done
return 1
}
if ! observe_provisioned_swap; then
fail "unable to verify the required active swap capacity after bounded observation"
fi
trap - EXIT
printf 'Hermes E2E swap ready: %s bytes active\n' "${active_swap_bytes}"
/usr/bin/sudo -n /usr/sbin/swapon --show
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && (matrix.agent == 'hermes' || matrix.agent == 'deepagents') }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- 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
- name: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && (matrix.agent == 'hermes' || matrix.agent == 'deepagents') }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- 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
openshell-credential-generation-window:
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,') || contains(format(',{0},', inputs.jobs), ',openshell-credential-generation-window,') || contains(format(',{0},', inputs.targets), ',openshell-credential-generation-window,') }}
runs-on: ubuntu-latest
permissions:
contents: read
# Keep the credential-generation lifecycle on a fresh runner so it can
# execute in parallel with, and fail independently from, the Deep Agents
# MCP lifecycle without sharing destructive sandbox state.
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openshell-credential-generation-window"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openshell-credential-generation-window
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Install and verify cloudflared prerequisite
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
mkdir -p "$E2E_ARTIFACT_DIR/openshell-credential-generation-window"
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/openshell-credential-generation-window/openshell-exact-main-provenance.json"
- name: Run OpenShell credential generation-window 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 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
- id: credential_window_artifact_secret_scan
name: Scan credential-window artifacts for fixture credentials
if: always()
run: >-
npx tsx tools/e2e/assert-mcp-artifact-secrets-absent.mts
e2e-artifacts/live/openshell-credential-generation-window
- name: Upload credential-window artifacts
if: ${{ always() && steps.credential_window_artifact_secret_scan.outcome == 'success' }}
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-openshell-credential-generation-window
path: e2e-artifacts/live/openshell-credential-generation-window/
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Install and verify cloudflared prerequisite
# Keep the public HTTPS routing fixture on the same reviewed binary as
# the MCP and tunnel-lifecycle lanes. The checksum and package metadata
# checks prevent a mutable package source from entering PR-safe E2E.
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: Run inference routing live test
# Direct PR-safe E2E coverage proves invalid-key, unreachable-endpoint,
# localhost-compatible routing, namespace-aware HTTPS pinning, DNS
# rebinding resistance, private-target redirect rejection, and
# credential-bearing URL state rejection without live provider quota.
# It also proves generic and Entra-shaped runtime identity through a
# deterministic public OAuth refresh, real OpenShell provider
# attachment/injection, Graph /v1.0/me policy, rotation, and rollback
# without storing a third-party tenant credential in PR CI.
# 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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:
- id: trusted_hermes_swap
name: Provision trusted Hermes E2E swap
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
DISPATCH_SHA: ${{ github.sha }}
ENV: /dev/null
EVENT_NAME: ${{ github.event_name }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
LC_ALL: C
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
RUNNER_ARCH_KIND: ${{ runner.arch }}
RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }}
RUNNER_OS_KIND: ${{ runner.os }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: *trusted-hermes-e2e-swap
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- 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: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['hermes-inference-switch'] }}
permissions:
contents: read
timeout-minutes: 55
strategy:
fail-fast: false
matrix:
include:
- 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_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:
- id: trusted_hermes_swap
name: Provision trusted Hermes E2E swap
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
DISPATCH_SHA: ${{ github.sha }}
ENV: /dev/null
EVENT_NAME: ${{ github.event_name }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
LC_ALL: C
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
RUNNER_ARCH_KIND: ${{ runner.arch }}
RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }}
RUNNER_OS_KIND: ${{ runner.os }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: *trusted-hermes-e2e-swap
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run Hermes inference switch 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/hermes-inference-switch.test.ts
- name: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
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@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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
dashboard-remote-bind:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',dashboard-remote-bind,') || contains(format(',{0},', inputs.targets), ',dashboard-remote-bind,') }}
runs-on: ubuntu-latest
timeout-minutes: 65
env:
E2E_JOB: "1"
E2E_TARGET_ID: "dashboard-remote-bind"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/dashboard-remote-bind
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_DASHBOARD_REMOTE_BIND: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-dashboard-remote-bind"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Run dashboard remote-bind 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/dashboard-remote-bind.test.ts
- name: Upload dashboard remote-bind 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['hermes-e2e'] }}
timeout-minutes: 85
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_E2E_HERMES_DASHBOARD: "1"
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:
- id: trusted_hermes_swap
name: Provision trusted Hermes E2E swap
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && (github.event_name == 'schedule' || inputs.checkout_sha == '' || (github.event_name == 'workflow_dispatch' && inputs.checkout_sha != '' && (contains(format(',{0},', inputs.jobs), ',hermes-e2e,') || contains(format(',{0},', inputs.targets), ',hermes-e2e,') || contains(format(',{0},', inputs.jobs), ',hermes-dashboard,') || contains(format(',{0},', inputs.targets), ',hermes-dashboard,')))) }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
DISPATCH_SHA: ${{ github.sha }}
ENV: /dev/null
EVENT_NAME: ${{ github.event_name }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
LC_ALL: C
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
RUNNER_ARCH_KIND: ${{ runner.arch }}
RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }}
RUNNER_OS_KIND: ${{ runner.os }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: *trusted-hermes-e2e-swap
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- 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: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- name: Checkout trusted Hermes GPU runtime fixture
if: ${{ matrix.scenario == 'fallback' }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
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@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Reassert trusted Node runtime
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.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-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: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['hermes-discord'] }}
timeout-minutes: 90
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:
- id: trusted_hermes_swap
name: Provision trusted Hermes E2E swap
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
DISPATCH_SHA: ${{ github.sha }}
ENV: /dev/null
EVENT_NAME: ${{ github.event_name }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
LC_ALL: C
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
RUNNER_ARCH_KIND: ${{ runner.arch }}
RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }}
RUNNER_OS_KIND: ${{ runner.os }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: *trusted-hermes-e2e-swap
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- 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: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- 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:
name: Network policy (${{ matrix.scenario }})
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
strategy:
fail-fast: false
matrix:
include:
- scenario: live-probes
selector: "^network-policy:.+probes$"
sandbox: e2e-net-policy-live-probes
env:
E2E_JOB: "1"
E2E_TARGET_ID: "network-policy"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/network-policy/${{ matrix.scenario }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_E2E_SHARD: ${{ matrix.scenario }}
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_E2E_USE_HOSTED_INFERENCE: "1"
NEMOCLAW_SANDBOX_NAME: ${{ matrix.sandbox }}
# 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
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@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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 \
--selector "${{ matrix.selector }}"
- name: Upload network-policy artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-network-policy-${{ matrix.scenario }}
path: e2e-artifacts/live/network-policy/${{ matrix.scenario }}/
- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
common-egress-agent:
name: Common egress agent (${{ matrix.scenario }})
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: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['common-egress-agent'] }}
timeout-minutes: 60
strategy:
fail-fast: false
max-parallel: 2
matrix:
include:
- scenario: openclaw-balanced-weather
selector: "^common-egress.+C1.+$"
- scenario: openclaw-open-reference
selector: "^common-egress.+C2.+$"
- scenario: hermes-open-reference
selector: "^common-egress.+C3.+$"
env:
E2E_JOB: "1"
E2E_TARGET_ID: "common-egress-agent"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/common-egress-agent/${{ matrix.scenario }}
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_E2E_SHARD: ${{ matrix.scenario }}
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:
- id: trusted_hermes_swap
name: Provision trusted Hermes E2E swap
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.scenario == 'hermes-open-reference' }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
DISPATCH_SHA: ${{ github.sha }}
ENV: /dev/null
EVENT_NAME: ${{ github.event_name }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
LC_ALL: C
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
RUNNER_ARCH_KIND: ${{ runner.arch }}
RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }}
RUNNER_OS_KIND: ${{ runner.os }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: *trusted-hermes-e2e-swap
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- 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 \
--selector "${{ matrix.selector }}"
- name: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- name: Upload common-egress agent artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-common-egress-agent-${{ matrix.scenario }}
path: e2e-artifacts/live/common-egress-agent/${{ matrix.scenario }}/
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
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: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['hermes-shields-config'] }}
timeout-minutes: 60
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:
- id: trusted_hermes_swap
name: Provision trusted Hermes E2E swap
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
DISPATCH_SHA: ${{ github.sha }}
ENV: /dev/null
EVENT_NAME: ${{ github.event_name }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
LC_ALL: C
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
RUNNER_ARCH_KIND: ${{ runner.arch }}
RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }}
RUNNER_OS_KIND: ${{ runner.os }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: *trusted-hermes-e2e-swap
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- 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: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['rebuild-hermes'] }}
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- 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: 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: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- 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: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)['rebuild-hermes-stale-base'] }}
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- 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: 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: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- 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
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
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@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)[format('security-posture-{0}', matrix.agent)] }}
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:
- id: trusted_hermes_swap
name: Provision trusted Hermes E2E swap
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.agent == 'hermes' }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
DISPATCH_SHA: ${{ github.sha }}
ENV: /dev/null
EVENT_NAME: ${{ github.event_name }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
LC_ALL: C
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
RUNNER_ARCH_KIND: ${{ runner.arch }}
RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }}
RUNNER_OS_KIND: ${{ runner.os }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: *trusted-hermes-e2e-swap
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
with:
build-cli: "false"
- name: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && matrix.agent == 'hermes' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- 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: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && matrix.agent == 'hermes' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
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@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
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.jobs), ',sandbox-rlimits-connect,') || contains(format(',{0},', inputs.targets), ',sandbox-operations,') || contains(format(',{0},', inputs.targets), ',sandbox-rlimits-connect,') }}
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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
# Scheduled release-baseline coverage for #6108. This runs independently of
# the current lifecycle lane so the exact v0.0.71 build does not extend that
# lane's critical path.
openclaw-plugin-runtime-exdev-release:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',openclaw-plugin-runtime-exdev-release,') || contains(format(',{0},', inputs.targets), ',openclaw-plugin-runtime-exdev-release,') }}
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 55
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openclaw-plugin-runtime-exdev-release"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openclaw-plugin-runtime-exdev-release
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-release"
OPENSHELL_GATEWAY: "nemoclaw"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
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:2d9f5c76c8f4dd36e8f253bee5d828a83a6c09f36188f0b0414325232e0b175d
- 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@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Run OpenClaw custom-plugin release baseline 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 \
--selector release-baseline
- name: Upload OpenClaw plugin release baseline 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 current-lifecycle coverage for #6108 / #3513 / #3127. This
# proves restart/rebuild persistence and target-side runtime-dependency
# replacement across devices without waiting for the release baseline.
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
# Two bounded 25-minute onboards plus the 20-minute rebuild and 15-minute
# Vitest buffer need 85 minutes; allow 20 more for setup and teardown.
timeout-minutes: 105
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
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@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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 \
--selector current-lifecycle
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
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@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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: 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_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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Run OpenClaw inference switch live test
# Preserves the real install.sh/OpenShell/Docker/managed-proxy boundary while
# Vitest owns the route, config, registry, inference.local, and agent
# assertions.
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:
- id: trusted_hermes_swap
name: Provision trusted Hermes E2E swap
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.agent == 'hermes' }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
DISPATCH_SHA: ${{ github.sha }}
ENV: /dev/null
EVENT_NAME: ${{ github.event_name }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
LC_ALL: C
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
RUNNER_ARCH_KIND: ${{ runner.arch }}
RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }}
RUNNER_OS_KIND: ${{ runner.os }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: *trusted-hermes-e2e-swap
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Run Bedrock Runtime compatible Anthropic live test
# Preserves the fake Bedrock Runtime endpoint, /etc/hosts mapping,
# source CLI onboard, OpenShell adapter route, agent runtime probes,
# and leak-scan contract.
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
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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: ${{ fromJSON(needs.generate-matrix.outputs.runner_routing)[format('channels-stop-start-{0}', matrix.agent)] }}
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:
- id: trusted_hermes_swap
name: Provision trusted Hermes E2E swap
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.agent == 'hermes' }}
shell: /bin/bash --noprofile --norc -e -o pipefail {0}
env:
BASH_ENV: /dev/null
CHECKOUT_SHA: ${{ inputs.checkout_sha }}
DISPATCH_SHA: ${{ github.sha }}
ENV: /dev/null
EVENT_NAME: ${{ github.event_name }}
EXPECTED_WORKFLOW_SHA: ${{ inputs.workflow_sha }}
LC_ALL: C
REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
RUNNER_ARCH_KIND: ${{ runner.arch }}
RUNNER_ENVIRONMENT_KIND: ${{ runner.environment }}
RUNNER_OS_KIND: ${{ runner.os }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: *trusted-hermes-e2e-swap
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Initialize runner comparison telemetry
if: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && matrix.agent == 'hermes' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts initialize
- 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: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && matrix.agent == 'hermes' }}
continue-on-error: true
shell: bash
run: npx tsx tools/e2e/runner-comparison.mts finalize
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
- *dockerhub-auth
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
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
timeout-minutes: 15
# 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
[
base-image-publication,
generate-matrix,
retired-selector-compatibility,
staging-brev-launchable,
live,
shared-e2e,
openshell-gateway-auth-contract,
mcp-bridge,
openshell-credential-generation-window,
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,
dashboard-remote-bind,
sessions-agents-cli,
hermes-e2e,
hermes-gpu-startup,
hermes-slack,
hermes-discord,
network-policy,
common-egress-agent,
shields-config,
hermes-shields-config,
rebuild-openclaw,
rebuild-hermes,
rebuild-hermes-stale-base,
overlayfs-autofix,
state-backup-restore,
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,
snapshot-commands,
openclaw-plugin-runtime-exdev-release,
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,
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.workflow_sha }}
persist-credentials: false
sparse-checkout: |
tools/e2e/report-e2e-results.mts
tools/e2e/selector-aliases.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 }}
NEEDS_JSON: ${{ toJSON(needs) }}
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 = JSON.parse(process.env.NEEDS_JSON || '{}');
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
timeout-minutes: 15
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false
sparse-checkout: |
ci/onboard-performance-budget.json
scripts/audit-test-runtime.mts
scripts/scorecard
sparse-checkout-cone-mode: false
- name: Download E2E progress artifacts
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: ${{ runner.temp }}/e2e-runtime-audit
pattern: e2e-*
- 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 }}
RUNTIME_ARTIFACTS: ${{ runner.temp }}/e2e-runtime-audit
RUNTIME_SUMMARY_FILE: ${{ runner.temp }}/e2e-runtime-summary.json
TARGETS: ${{ inputs.targets }}
NEEDS_JSON: ${{ toJSON(needs) }}
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 runtimeAudit = require(
path.join(process.env.GITHUB_WORKSPACE, 'scripts/audit-test-runtime.mts'),
);
const runtimeHistory = require(
path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/analyze-runtime-history.mts'),
);
const firstTurnLatency = require(
path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/analyze-first-turn-latency.mts'),
);
const needs = JSON.parse(process.env.NEEDS_JSON || '{}');
// 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 });
let runtimeSummaryMarkdown;
let runtimeHistoryRows = null;
try {
const runtimeRows = runtimeAudit.auditTestRuntime([process.env.RUNTIME_ARTIFACTS]);
runtimeSummaryMarkdown = runtimeAudit.formatRuntimeAuditSummary(runtimeRows);
runtimeHistoryRows = runtimeAudit.collectRuntimeHistorySamples([
process.env.RUNTIME_ARTIFACTS,
]);
} catch {
core.warning('E2E test phase runtime summary unavailable: invalid progress artifact');
runtimeSummaryMarkdown = [
'## E2E Test Phase Runtime',
'',
'The summary is unavailable because a `test-progress.json` artifact was invalid.',
'',
].join('\n');
}
const runtimeHistoryMarkdown = runtimeHistoryRows === null
? [
'## E2E Nightly Runtime Trend',
'',
'The trend is unavailable because a `test-progress.json` artifact was invalid.',
'',
].join('\n')
: await runtimeHistory.buildRuntimeHistory(
{ github, context, core },
runtimeHistoryRows,
process.env.RUNTIME_SUMMARY_FILE,
{
currentFirstTurnLatency:
firstTurnLatency.readCurrentFirstTurnLatencySample(
process.env.RUNTIME_ARTIFACTS,
),
loadPriorNightlySummaries: runtimeHistory.loadPriorNightlySummaries,
},
);
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}\n\n${runtimeSummaryMarkdown}\n${runtimeHistoryMarkdown}`)
.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}`);
}
- name: Upload E2E runtime summary
if: ${{ always() && github.event_name == 'schedule' }}
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-runtime-summary
path: ${{ runner.temp }}/e2e-runtime-summary.json