-
Notifications
You must be signed in to change notification settings - Fork 2.9k
2430 lines (2328 loc) · 113 KB
/
Copy pathnightly-e2e.yaml
File metadata and controls
2430 lines (2328 loc) · 113 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Nightly E2E tests:
#
# cloud-e2e Cloud inference (NVIDIA Endpoint API) on ubuntu-latest.
# messaging-providers-e2e Validates messaging credential provider/placeholder/L7-proxy chain
# for Telegram + Discord + Slack. Uses fake tokens. Slack additionally
# exercises OpenShell provider-shaped alias resolution (#2085 follow-up).
# openclaw-slack-pairing-e2e
# Validates hermetic Slack Socket Mode pairing request approval across
# gateway and connect-shell OpenClaw state roots (#3730/#3737).
# openclaw-discord-pairing-e2e
# Validates hermetic Discord pairing request approval across
# gateway and connect-shell OpenClaw state roots (#4061).
# issue-4462-scope-upgrade-approval-e2e
# Validates real CLI scope-upgrade approval and confirms
# the approved agent run stays on gateway mode (#4462).
# issue-4462-gateway-pinned-approval-characterization-e2e
# Characterizes legacy gateway-pinned scope approval
# against a real sandbox, then recovers with the fix.
# messaging-compatible-endpoint-e2e
# Validates Telegram + OpenAI-compatible endpoint inference routing
# through inference.local with a hermetic local mock (#2766).
# kimi-inference-compat-e2e
# Validates Kimi K2.6 safe exec splitting through OpenClaw trajectories
# with a hermetic OpenAI-compatible mock (#2620).
# bedrock-runtime-compatible-anthropic-e2e
# Validates the silent Bedrock Runtime custom Anthropic endpoint path
# through a hermetic fake Bedrock Runtime host for OpenClaw and Hermes.
# token-rotation-e2e Validates that rotating a messaging token and re-running onboard
# propagates the new credential to the sandbox. Combined Telegram +
# Discord + Slack coverage with cross-talk assertions. See issue #1903.
# sandbox-survival-e2e Sandbox survival across gateway restarts (onboard, inference,
# gateway stop/start, verify sandbox + workspace + inference).
# openshell-gateway-upgrade-e2e
# Validates real v0.0.36 curl install upgrade into
# the current supported OpenShell with pre-upgrade backup, restored
# agent state, and the same agent type running.
# hermes-e2e Hermes Agent E2E — install → onboard --agent hermes → health
# probe → live inference. Validates the multi-agent architecture.
# hermes-dashboard-e2e Hermes Agent E2E with optional web dashboard enabled,
# validating API/dashboard forwards and host reachability.
# hermes-root-entrypoint-smoke-e2e
# Builds the real Hermes image and verifies root entrypoint startup,
# gateway-user execution, v0.14 layout repair, and PID migration.
# openclaw-onboard-security-posture-e2e
# Full OpenClaw onboard on a non-root host user
# with trusted rc-file and runtime guard assertions.
# hermes-onboard-security-posture-e2e
# Full Hermes onboard on a non-root host user
# with trusted rc-file and runtime guard assertions.
# hermes-inference-switch-e2e
# Switches a running Hermes sandbox with `nemohermes inference set`
# and verifies route, config.yaml, hashes, and live requests.
# hermes-discord-e2e Hermes Discord onboarding — validates the top-level Hermes
# Discord schema plus OpenShell placeholder/token isolation.
# hermes-slack-e2e Hermes Slack onboarding — validates the Hermes Slack policy,
# Slack providers, and OpenShell credential rewrite path.
# openclaw-inference-switch-e2e
# Switches a running OpenClaw sandbox with `nemoclaw inference set`
# and verifies route, openclaw.json, hashes, and live requests.
# credential-migration-e2e Validates legacy ~/.nemoclaw/credentials.json migration to the
# OpenShell gateway, secure zero-fill on unlink, allowlist filter
# on non-credential env keys, and symlink-safe deletion.
# launchable-smoke-e2e Community install path (brev-launchable-ci-cpu.sh) on ubuntu-latest.
# gpu-e2e Local Ollama inference on an NVKS ephemeral GPU runner.
# gpu-double-onboard-e2e Ollama proxy token consistency after re-onboard (#2553).
# notify-on-failure Auto-creates a GitHub issue when any E2E job fails.
#
# Runs directly on the runner (not inside Docker) because OpenShell bootstraps
# a K3s cluster inside a privileged Docker container — nesting would break networking.
#
# NVIDIA_API_KEY for cloud-e2e:
# - Repository secret: Settings → Secrets and variables → Actions → Repository secrets.
# - Environment secret: only available if the job sets `environment: <that environment name>`.
# (Storing the key under Environments / NVIDIA_API_KEY without `environment:` here leaves the
# variable empty in the job — repository secrets and environment secrets are separate.)
# Only runs on schedule and manual dispatch — never on PRs (secret protection).
name: E2E / Nightly
run-name: >-
${{ github.event_name == 'workflow_dispatch' && inputs.advisor_dispatch_id != '' && format('E2E / Nightly ({0})', inputs.advisor_dispatch_id) || 'E2E / Nightly' }}
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
inputs:
jobs:
description: >-
Comma-separated job names to run (empty = all).
Valid: cloud-e2e, cloud-onboard-e2e, cloud-inference-e2e,
skill-agent-e2e, docs-validation-e2e, messaging-providers-e2e,
openclaw-slack-pairing-e2e,
openclaw-tui-chat-correlation-e2e,
issue-3600-gpu-proof-optional-e2e,
openclaw-discord-pairing-e2e,
issue-4462-scope-upgrade-approval-e2e,
issue-4462-gateway-pinned-approval-characterization-e2e,
messaging-compatible-endpoint-e2e,
kimi-inference-compat-e2e,
bedrock-runtime-compatible-anthropic-e2e,
token-rotation-e2e, sandbox-survival-e2e,
openshell-gateway-upgrade-e2e,
issue-2478-crash-loop-recovery-e2e, hermes-e2e,
hermes-dashboard-e2e,
hermes-root-entrypoint-smoke-e2e,
openclaw-onboard-security-posture-e2e,
hermes-onboard-security-posture-e2e,
hermes-inference-switch-e2e, hermes-discord-e2e,
hermes-slack-e2e, sandbox-operations-e2e, inference-routing-e2e,
openclaw-inference-switch-e2e,
network-policy-e2e, state-backup-restore-e2e, tunnel-lifecycle-e2e, diagnostics-e2e,
credential-migration-e2e,
snapshot-commands-e2e, shields-config-e2e,
vm-driver-privileged-exec-routing-e2e, rebuild-openclaw-e2e,
upgrade-stale-sandbox-e2e, rebuild-hermes-e2e,
rebuild-hermes-stale-base-e2e, double-onboard-e2e,
onboard-repair-e2e, onboard-resume-e2e, onboard-negative-paths-e2e,
runtime-overrides-e2e,
credential-sanitization-e2e, telegram-injection-e2e,
overlayfs-autofix-e2e, device-auth-health-e2e,
launchable-smoke-e2e, gpu-e2e, gpu-double-onboard-e2e,
channels-add-remove-e2e, channels-stop-start-e2e, brave-search-e2e
required: false
type: string
default: ""
target_ref:
description: >-
Optional branch, ref, or SHA to test. When empty, tests run against
the workflow ref selected for the dispatch. Used by e2e-advisor
auto-dispatch so the trusted main workflow can test a PR head SHA.
required: false
type: string
default: ""
pr_number:
description: Optional PR number for selective-dispatch result comments.
required: false
type: string
default: ""
advisor_dispatch_id:
description: Optional correlation ID from e2e-advisor auto-dispatch.
required: false
type: string
default: ""
permissions:
contents: read
concurrency:
group: nightly-e2e-${{ github.event_name }}-${{ github.event_name == 'workflow_dispatch' && format('{0}-{1}', github.ref, inputs.pr_number || 'manual') || 'schedule' }}
cancel-in-progress: true
# Selective-dispatch contract: tools/e2e-advisor/dispatch.mts discovers
# dispatchable jobs by looking for each job's exact predicate shape below:
# github.event_name != 'workflow_dispatch' || inputs.jobs == '' ||
# contains(format(',{0},', inputs.jobs), ',<job-id>,')
# Keep this predicate format in sync with test/e2e-advisor-dispatch.test.ts if
# the workflow changes how individual jobs opt in to selective dispatch.
jobs:
cloud-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',cloud-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-full-e2e.sh
artifact_name: "install-log"
artifact_path: "/tmp/nemoclaw-e2e-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-nightly"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
cloud-onboard-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',cloud-onboard-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-cloud-onboard-e2e.sh
artifact_name: "install-log-cloud-onboard"
artifact_path: "/tmp/nemoclaw-e2e-cloud-onboard-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_POLICY_MODE":"custom","NEMOCLAW_POLICY_PRESETS":"npm,pypi","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-cloud-onboard"}'
checked_out_ref_env: "NEMOCLAW_PUBLIC_INSTALL_REF"
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
cloud-inference-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',cloud-inference-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-cloud-inference-e2e.sh
timeout_minutes: 30
artifact_name: "install-log-cloud-inference"
artifact_path: "/tmp/nemoclaw-e2e-cloud-inference-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-cloud-inference"}'
nvidia_api_key: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
skill-agent-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',skill-agent-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-skill-agent-e2e.sh
timeout_minutes: 30
artifact_name: "install-log-skill-agent"
artifact_path: "/tmp/nemoclaw-e2e-skill-agent-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-skill-agent"}'
nvidia_api_key: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
docs-validation-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' &&
(github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',docs-validation-e2e,'))
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.target_ref || github.ref }}
- name: Install NemoClaw
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
run: bash install.sh --non-interactive --yes-i-accept-third-party-software
- name: Run docs validation
env:
CHECK_DOC_LINKS_REMOTE: "0"
run: |
set -euo pipefail
[ -f "$HOME/.bashrc" ] && source "$HOME/.bashrc" 2>/dev/null || true
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
[ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]] && export PATH="$HOME/.local/bin:$PATH"
bash test/e2e/test-docs-validation.sh
# ── Messaging Providers E2E ──────────────────────────────────
# Validates the full provider/placeholder/L7-proxy chain for token-backed
# messaging credentials, and the QR-only WhatsApp config/policy/no-provider
# path. Uses fake tokens by default — the L7 proxy rewrites placeholders and
# the real API returns 401, proving the chain works. See: PR #1081
messaging-providers-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',messaging-providers-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-messaging-providers.sh
timeout_minutes: 75
artifact_name: "install-log-messaging-providers"
artifact_path: |
/tmp/nemoclaw-e2e-install.log
/tmp/nemoclaw-e2e-whatsapp-*.log
env_json: '{"DISCORD_BOT_TOKEN":"test-fake-discord-token-e2e","NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_POLICY_TIER":"open","NEMOCLAW_SANDBOX_NAME":"e2e-msg-provider","SLACK_APP_TOKEN":"xapp-fake-slack-app-token-e2e","SLACK_BOT_TOKEN":"xoxb-fake-slack-token-e2e","TELEGRAM_BOT_TOKEN":"test-fake-telegram-token-e2e"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
TELEGRAM_BOT_TOKEN_REAL: ${{ secrets.TELEGRAM_BOT_TOKEN_REAL }}
TELEGRAM_CHAT_ID_E2E: ${{ secrets.TELEGRAM_CHAT_ID_E2E }}
DISCORD_BOT_TOKEN_REAL: ${{ secrets.DISCORD_BOT_TOKEN_REAL }}
DISCORD_CHANNEL_ID_E2E: ${{ secrets.DISCORD_CHANNEL_ID_E2E }}
SLACK_BOT_TOKEN_REAL: ${{ secrets.SLACK_BOT_TOKEN_REAL }}
SLACK_APP_TOKEN_REAL: ${{ secrets.SLACK_APP_TOKEN_REAL }}
SLACK_CHANNEL_ID_E2E: ${{ secrets.SLACK_CHANNEL_ID_E2E }}
openclaw-slack-pairing-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',openclaw-slack-pairing-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-openclaw-slack-pairing.sh
artifact_name: "install-log-openclaw-slack-pairing"
artifact_path: "/tmp/nemoclaw-e2e-openclaw-slack-pairing-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_POLICY_TIER":"open","NEMOCLAW_SANDBOX_NAME":"e2e-openclaw-slack-pairing","SLACK_APP_TOKEN":"xapp-fake-slack-pairing-e2e","SLACK_BOT_TOKEN":"xoxb-fake-slack-pairing-e2e"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
openclaw-tui-chat-correlation-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' &&
(github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',openclaw-tui-chat-correlation-e2e,'))
runs-on: ubuntu-latest
timeout-minutes: 75
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.target_ref || github.ref }}
- name: Resolve public install ref
id: public_install_ref
shell: bash
run: |
printf 'ref=%s\n' "$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0
with:
node-version: "22"
- name: Install test dependencies
run: npm ci --include=dev
- name: Run OpenClaw TUI chat correlation E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-openclaw-tui-correlation"
NEMOCLAW_PUBLIC_INSTALL_REF: ${{ steps.public_install_ref.outputs.ref }}
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-openclaw-tui-chat-correlation.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: install-log-openclaw-tui-chat-correlation
path: /tmp/nemoclaw-e2e-openclaw-tui-correlation-install.log
if-no-files-found: ignore
# ── DGX Station GPU optional proof validation (#3600) ──────────
# CI cannot emulate GB300, but this guards the release-blocker mitigation:
# optional direct GPU proofs must not abort onboard before the fatal throw.
issue-3600-gpu-proof-optional-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' &&
(github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',issue-3600-gpu-proof-optional-e2e,'))
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.target_ref || github.ref }}
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0
with:
node-version: "22"
- name: Install test dependencies
run: npm ci --include=dev
- name: Verify optional GPU proof cannot abort onboard
run: npx vitest run src/lib/onboard/sandbox-gpu-preflight.test.ts --pool=forks -t "direct sandbox GPU proof"
# ── OpenClaw Discord Pairing E2E (#4061) ──────────────────────
# Hermetic Discord Gateway placeholder rewrite proof, then connect-shell
# `openclaw pairing approve discord <code>` against shared state.
openclaw-discord-pairing-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',openclaw-discord-pairing-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-openclaw-discord-pairing.sh
artifact_name: "install-log-openclaw-discord-pairing"
artifact_path: "/tmp/nemoclaw-e2e-openclaw-discord-pairing-install.log"
env_json: '{"DISCORD_BOT_TOKEN":"test-fake-discord-pairing-e2e","NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_POLICY_TIER":"open","NEMOCLAW_SANDBOX_NAME":"e2e-openclaw-discord-pairing"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
# ── OpenClaw Scope-Upgrade Approval E2E (#4462) ────────────────
# Positive proof: in a real sandbox, accept either a visible pending CLI
# scope upgrade or the fixed watcher's immediate approval, then confirm
# openclaw agent still uses the gateway path rather than embedded fallback.
issue-4462-scope-upgrade-approval-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',issue-4462-scope-upgrade-approval-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-issue-4462-scope-upgrade-approval.sh
timeout_minutes: 60
artifact_name: "issue-4462-scope-upgrade-approval-logs"
artifact_path: |
/tmp/nemoclaw-e2e-issue-4462-scope-upgrade-install.log
/tmp/nemoclaw-issue-4462-scope-upgrade-approval.log
/tmp/nemoclaw-issue-4462-scope-upgrade-agent.log
/tmp/nemoclaw-issue-4462-scope-upgrade-state.log
env_json: '{"NEMOCLAW_4462_MODE":"approval","NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_AUTO_PAIR_DEADLINE_SECS":"30","NEMOCLAW_AUTO_PAIR_FAST_DEADLINE_SECS":"3","NEMOCLAW_AUTO_PAIR_SLOW_INTERVAL_SECS":"600","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-issue-4462-scope-upgrade"}'
nvidia_api_key: true
github_token: false
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
# ── OpenClaw Gateway-Pinned Approval Characterization (#4462) ──
# Diagnostic proof: in a real sandbox, wait for the fixed watcher to exit,
# force the legacy gateway-pinned approve path, record the observed
# OpenClaw outcome, and recover through the fixed proxy-env guard if needed.
issue-4462-gateway-pinned-approval-characterization-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',issue-4462-gateway-pinned-approval-characterization-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-issue-4462-scope-upgrade-approval.sh
timeout_minutes: 60
artifact_name: "issue-4462-gateway-pinned-approval-characterization-logs"
artifact_path: |
/tmp/nemoclaw-e2e-issue-4462-scope-upgrade-repro-install.log
/tmp/nemoclaw-issue-4462-scope-upgrade-repro-approval.log
/tmp/nemoclaw-issue-4462-scope-upgrade-repro-agent.log
/tmp/nemoclaw-issue-4462-scope-upgrade-repro-state.log
env_json: '{"NEMOCLAW_4462_AGENT_LOG":"/tmp/nemoclaw-issue-4462-scope-upgrade-repro-agent.log","NEMOCLAW_4462_APPROVAL_LOG":"/tmp/nemoclaw-issue-4462-scope-upgrade-repro-approval.log","NEMOCLAW_4462_AUTO_PAIR_DEADLINE_SECS":"12","NEMOCLAW_4462_AUTO_PAIR_FAST_DEADLINE_SECS":"1","NEMOCLAW_4462_AUTO_PAIR_RUN_TIMEOUT_SECS":"2","NEMOCLAW_4462_AUTO_PAIR_SLOW_INTERVAL_SECS":"1","NEMOCLAW_4462_INSTALL_LOG":"/tmp/nemoclaw-e2e-issue-4462-scope-upgrade-repro-install.log","NEMOCLAW_4462_MODE":"legacy-repro","NEMOCLAW_4462_STATE_LOG":"/tmp/nemoclaw-issue-4462-scope-upgrade-repro-state.log","NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-issue-4462-scope-upgrade-repro"}'
nvidia_api_key: true
github_token: false
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
messaging-compatible-endpoint-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',messaging-compatible-endpoint-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-messaging-compatible-endpoint.sh
artifact_name: "install-log-messaging-compatible-endpoint"
artifact_path: "/tmp/nemoclaw-e2e-messaging-compatible-endpoint-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_SANDBOX_NAME":"e2e-msg-compat","TELEGRAM_ALLOWED_IDS":"123456789","TELEGRAM_BOT_TOKEN":"test-fake-telegram-token-e2e"}'
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
# ── Channels add/remove lifecycle E2E (#3462 Test 2) ────────────────
# Regression coverage for #3437 (channels add must auto-apply the matching
# network policy preset so the bridge boots with egress to its upstream API)
# and #3671 (channels remove must detach providers, un-apply the preset,
# and survive a follow-up rebuild without being silently re-added from
# shell env). Telegram-only — the other paste-token channels walk the same
# KNOWN_CHANNELS + preset lookup code path.
channels-add-remove-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',channels-add-remove-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-channels-add-remove.sh
timeout_minutes: 75
artifact_name: "install-log-channels-add-remove"
artifact_path: |
/tmp/nemoclaw-e2e-install.log
/tmp/nc-add.log
/tmp/nc-remove.log
/tmp/nc-rebuild-add.log
/tmp/nc-rebuild-remove.log
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_SANDBOX_NAME":"e2e-channels-add-remove","TELEGRAM_BOT_TOKEN":"test-fake-telegram-token-add-remove-e2e"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
# ── Channels stop/start lifecycle E2E (#3462) ───────────────────────
# Regression coverage for #3453 (stop must disable across rebuild), #3381
# (start must re-attach from cached credentials).
# Exercises OpenClaw and Hermes across telegram, discord, wechat, slack, and whatsapp.
channels-stop-start-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',channels-stop-start-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-channels-stop-start.sh
timeout_minutes: 120
artifact_name: "install-log-channels-stop-start"
artifact_path: |
/tmp/nemoclaw-e2e-install.log
/tmp/nemoclaw-e2e-channels-*-install.log
/tmp/nc-channels-*.log
env_json: '{"DISCORD_ALLOWED_IDS":"1005536447329222676","DISCORD_BOT_TOKEN":"test-fake-discord-token-stop-start-e2e","DISCORD_REQUIRE_MENTION":"0","DISCORD_SERVER_ID":"1491590992753590594","NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_POLICY_TIER":"open","NEMOCLAW_SANDBOX_NAME":"e2e-channels-stop-start","SLACK_ALLOWED_USERS":"U0123456789,U09ABCDEFGH","SLACK_APP_TOKEN":"xapp-fake-slack-app-token-stop-start-e2e","SLACK_BOT_TOKEN":"xoxb-fake-slack-token-stop-start-e2e","TELEGRAM_ALLOWED_IDS":"123456789","TELEGRAM_BOT_TOKEN":"test-fake-telegram-token-stop-start-e2e","WECHAT_ACCOUNT_ID":"e2e-fake-account-stop-start","WECHAT_ALLOWED_IDS":"wxid_stopstart_operator","WECHAT_BASE_URL":"https://ilinkai-fake-stop-start.wechat.com","WECHAT_BOT_TOKEN":"test-fake-wechat-token-stop-start-e2e","WECHAT_USER_ID":"wxid_stopstart_operator"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
brave-search-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',brave-search-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-brave-search-e2e.sh
artifact_name: "install-log-brave-search"
artifact_path: "/tmp/nemoclaw-e2e-brave-search-onboard.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_SANDBOX_NAME":"e2e-brave-search"}'
brave_api_key: true
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
kimi-inference-compat-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' &&
(github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',kimi-inference-compat-e2e,'))
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.target_ref || github.ref }}
- name: Run Kimi inference compatibility E2E test
env:
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_SANDBOX_NAME: "e2e-kimi-compat"
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-kimi-inference-compat.sh
- name: Upload onboard log on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: install-log-kimi-inference-compat
path: /tmp/nemoclaw-e2e-kimi-inference-compat-onboard.log
if-no-files-found: ignore
- name: Upload build/setup log on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: build-log-kimi-inference-compat
path: /tmp/nemoclaw-e2e-kimi-inference-compat-build.log
if-no-files-found: ignore
- name: Upload agent log on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agent-log-kimi-inference-compat
path: /tmp/nemoclaw-e2e-kimi-inference-compat-agent.log
if-no-files-found: ignore
# ── Bedrock Runtime compatible Anthropic endpoint (#3767) ─────
# Hermetic fake Bedrock Runtime endpoint path. The sandbox only sees
# inference.local; the host-side OpenShell provider owns the hidden adapter
# token and the upstream Bedrock bearer derived from the fake pasted key.
bedrock-runtime-compatible-anthropic-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' &&
(github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',bedrock-runtime-compatible-anthropic-e2e,'))
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
agent: [openclaw, hermes]
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.target_ref || github.ref }}
- name: Run Bedrock Runtime compatible Anthropic E2E test
env:
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_AGENT: ${{ matrix.agent }}
NEMOCLAW_SANDBOX_NAME: e2e-bedrock-${{ matrix.agent }}
GITHUB_TOKEN: ${{ github.token }}
run: bash test/e2e/test-bedrock-runtime-compatible-anthropic.sh
- name: Upload onboard log on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: onboard-log-bedrock-runtime-compatible-anthropic-${{ matrix.agent }}
path: /tmp/nemoclaw-e2e-bedrock-runtime-${{ matrix.agent }}-onboard.log
if-no-files-found: ignore
- name: Upload build/setup log on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: build-log-bedrock-runtime-compatible-anthropic-${{ matrix.agent }}
path: /tmp/nemoclaw-e2e-bedrock-runtime-${{ matrix.agent }}-build.log
if-no-files-found: ignore
- name: Upload fake Bedrock Runtime log on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mock-log-bedrock-runtime-compatible-anthropic-${{ matrix.agent }}
path: /tmp/nemoclaw-e2e-bedrock-runtime-${{ matrix.agent }}-mock.log
if-no-files-found: ignore
- name: Upload Bedrock Runtime adapter log on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: adapter-log-bedrock-runtime-compatible-anthropic-${{ matrix.agent }}
path: ~/.nemoclaw/bedrock-runtime-adapter.log
if-no-files-found: ignore
# ── Token rotation (credential propagation to L7 proxy) ─────
# Validates that rotating a messaging token and re-running onboard
# propagates the new credential to the sandbox. Uses two fake tokens
# per provider (Telegram + Discord) to prove the sandbox is rebuilt on
# rotation and reused when unchanged.
# See: issue #1903
token-rotation-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' &&
(github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',token-rotation-e2e,'))
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.target_ref || github.ref }}
- name: Run token rotation E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_POLICY_TIER: "open"
GITHUB_TOKEN: ${{ github.token }}
TELEGRAM_BOT_TOKEN_A: "test-fake-token-A-rotation-e2e"
TELEGRAM_BOT_TOKEN_B: "test-fake-token-B-rotation-e2e"
DISCORD_BOT_TOKEN_A: "test-fake-discord-A-rotation-e2e"
DISCORD_BOT_TOKEN_B: "test-fake-discord-B-rotation-e2e"
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: bash test/e2e/test-token-rotation.sh
- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: install-log-token-rotation
path: /tmp/nemoclaw-e2e-install.log
if-no-files-found: ignore
# ── Sandbox survival (gateway restart recovery) ──────────────
sandbox-survival-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',sandbox-survival-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-sandbox-survival.sh
timeout_minutes: 30
artifact_name: "sandbox-survival-install-log"
artifact_path: "/tmp/nemoclaw-e2e-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_SANDBOX_NAME":"e2e-survival"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
issue-2478-crash-loop-recovery-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',issue-2478-crash-loop-recovery-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-issue-2478-crash-loop-recovery.sh
timeout_minutes: 30
artifact_name: "issue-2478-crash-loop-recovery-install-log"
artifact_path: "/tmp/nemoclaw-e2e-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_SANDBOX_NAME":"e2e-2478"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
hermes-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',hermes-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-hermes-e2e.sh
timeout_minutes: 60
artifact_name: "hermes-e2e-install-log"
artifact_path: "/tmp/nemoclaw-e2e-hermes-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_AGENT":"hermes","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-hermes"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
hermes-dashboard-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',hermes-dashboard-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-hermes-e2e.sh
timeout_minutes: 60
artifact_name: "hermes-dashboard-e2e-install-log"
artifact_path: "/tmp/nemoclaw-e2e-hermes-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_AGENT":"hermes","NEMOCLAW_E2E_HERMES_DASHBOARD":"1","NEMOCLAW_HERMES_DASHBOARD":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-hermes-dashboard"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
hermes-root-entrypoint-smoke-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',hermes-root-entrypoint-smoke-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-hermes-root-entrypoint-smoke.sh
timeout_minutes: 45
artifact_name: "hermes-root-entrypoint-smoke-log"
artifact_path: "/tmp/nemoclaw-hermes-root-entrypoint-smoke.log"
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
openclaw-onboard-security-posture-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',openclaw-onboard-security-posture-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-full-e2e.sh
timeout_minutes: 60
artifact_name: "openclaw-onboard-security-posture-install-log"
artifact_path: "/tmp/nemoclaw-e2e-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_E2E_EXPECT_NON_ROOT_HOST":"1","NEMOCLAW_E2E_SECURITY_POSTURE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-openclaw-security-posture"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
hermes-onboard-security-posture-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',hermes-onboard-security-posture-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-hermes-e2e.sh
timeout_minutes: 60
artifact_name: "hermes-onboard-security-posture-install-log"
artifact_path: "/tmp/nemoclaw-e2e-hermes-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_AGENT":"hermes","NEMOCLAW_E2E_EXPECT_NON_ROOT_HOST":"1","NEMOCLAW_E2E_SECURITY_POSTURE":"1","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-hermes-security-posture"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
hermes-inference-switch-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',hermes-inference-switch-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-hermes-inference-switch.sh
timeout_minutes: 60
artifact_name: "hermes-inference-switch-install-log"
artifact_path: "/tmp/nemoclaw-e2e-hermes-inference-switch-install.log"
env_json: '{"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE":"1","NEMOCLAW_AGENT":"hermes","NEMOCLAW_NON_INTERACTIVE":"1","NEMOCLAW_RECREATE_SANDBOX":"1","NEMOCLAW_SANDBOX_NAME":"e2e-hermes-inference-switch"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
hermes-discord-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',hermes-discord-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-hermes-discord-e2e.sh
timeout_minutes: 60
artifact_name: "hermes-discord-e2e-install-log"
artifact_path: "/tmp/nemoclaw-e2e-hermes-discord-install.log"
env_json: '{"DISCORD_ALLOWED_IDS":"1005536447329222676","DISCORD_BOT_TOKEN":"test-fake-discord-token-hermes-e2e","DISCORD_REQUIRE_MENTION":"0","DISCORD_SERVER_IDS":"1491590992753590594","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-discord"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
hermes-slack-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',hermes-slack-e2e,'))
uses: ./.github/workflows/e2e-script.yaml
with:
ref: ${{ inputs.target_ref || github.ref }}
script: test/e2e/test-hermes-slack-e2e.sh
runner: linux-amd64-cpu4
timeout_minutes: 60
artifact_name: "hermes-slack-e2e-install-log"
artifact_path: "/tmp/nemoclaw-e2e-hermes-slack-install.log"
env_json: '{"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","SLACK_APP_TOKEN":"xapp-test-hermes-slack-app-token","SLACK_BOT_TOKEN":"xoxb-test-hermes-slack-token"}'
nvidia_api_key: true
github_token: true
secrets:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
sandbox-operations-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' &&
(github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',sandbox-operations-e2e,'))
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.target_ref || github.ref }}
- name: Start gateway log streamer (background)
run: |
# Diagnostic for NVIDIA/NemoClaw#2484: container log driver in
# openshell's k3s setup doesn't allow reading container stdio —
# only working path to /tmp/gateway.log is via SSH, which
# `nemoclaw <sandbox> logs` uses internally.
#
# Snapshot mode (not follow): every 10s, overwrite per-sandbox
# log file with the latest gateway log content. Bounded output
# (~62 lines per snapshot). When a sandbox is destroyed by the
# test, the file holds the final pre-destroy snapshot.
mkdir -p docker-logs
nohup bash -c '
export PATH="$HOME/.local/bin:$PATH"
# Strategy: every 5s, snapshot each live sandbox via
# `docker exec openshell-cluster-nemoclaw kubectl ...`. This
# bypasses both per-pod networking (which has had connection-
# refused races for some sandboxes) and the host openshell
# client (which loses gateway metadata after TC-SBX-06s
# docker-kill). kubectl talks directly to k3s in the cluster
# container.
#
# Snapshot mode (overwrite per iteration), not live tail-F:
# the gateway-persistent.log file accumulates everything since
# boot (mirrored from /tmp/gateway.log by nemoclaw-start.sh),
# so a single full-cat at any point gives us complete history.
# Each iteration is short-lived so transient connection issues
# do not cause us to lose the entire stream.
#
# Also snapshot kubectl pod listing per iteration so we have
# the actual pod naming convention even if the cluster is
# destroyed by teardown later.
while sleep 5; do
if ! docker ps --format "{{.Names}}" 2>/dev/null | grep -q "^openshell-cluster-nemoclaw$"; then
continue
fi
docker exec openshell-cluster-nemoclaw kubectl get pods -A --no-headers >docker-logs/_pods.txt 2>&1
registry="$HOME/.nemoclaw/sandboxes.json"
[ -f "$registry" ] || continue
live=$(jq -r ".sandboxes // {} | keys[]?" "$registry" 2>/dev/null)
for name in $live; do
case "$name" in
*[!a-z0-9_-]*|"") continue ;;
esac
# Find pod by sandbox name. openshell uses the sandbox
# name as the namespace and "agent" as the pod name.
# Try a few common patterns.
pod_match=$(awk -v n="$name" "\$1==n || \$2==n || \$1==\"sandbox-\" n || \$2==\"sandbox-\" n {print \$1\"/\"\$2; exit}" docker-logs/_pods.txt)
if [ -z "$pod_match" ]; then
# Fallback: any pod whose name contains the sandbox name
pod_match=$(awk -v n="$name" "index(\$2,n)>0 {print \$1\"/\"\$2; exit}" docker-logs/_pods.txt)
fi
if [ -z "$pod_match" ]; then continue; fi
pod_ns="${pod_match%%/*}"
pod_name="${pod_match##*/}"
docker exec openshell-cluster-nemoclaw kubectl exec -n "$pod_ns" "$pod_name" -- bash -c "
for f in /sandbox/.openclaw/logs/gateway-persistent.log /tmp/gateway.log /tmp/openclaw-*/openclaw-*.log; do
[ -f \"\$f\" ] || continue
printf \"\\n----- %s (size=%s) -----\\n\" \"\$f\" \"\$(stat -c%s \"\$f\" 2>/dev/null || echo ?)\"
cat -- \"\$f\" 2>/dev/null
done
" > "docker-logs/sandbox-${name}.log" 2>&1
done
done
' >/dev/null 2>&1 &
echo $! > /tmp/gateway-log-streamer.pid
- name: Run sandbox operations E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_POLICY_TIER: "open"
GITHUB_TOKEN: ${{ github.token }}
# Override the 1800s default in test/e2e/e2e-timeout.sh. Sandbox
# creation alone is ~14 min per sandbox in current CI conditions
# (build+upload to k3s gateway), and the test creates two — leaving
# the default 30-min budget completely consumed by setup with no
# room for the actual TC-SBX cases. The job-level timeout (60 min,
# set in `timeout-minutes` above) is the real upper bound.
NEMOCLAW_E2E_TIMEOUT_SECONDS: "2700"
run: bash test/e2e/test-sandbox-operations.sh
- name: Stop gateway log streamer
if: always()
# Diagnostic step: never let `bash -e` kill the snapshot loop on a
# single command failure (openshell ssh-config, nemoclaw logs, etc.
# all routinely fail post-test depending on TC-SBX-06's docker-kill
# state). We log the failures inline and continue.
shell: bash --noprofile --norc -uo pipefail {0}
run: |
[ -f /tmp/gateway-log-streamer.pid ] && kill "$(cat /tmp/gateway-log-streamer.pid)" 2>/dev/null || true
# Kill any per-sandbox SSH+tail followers spawned by the streamer.
pkill -f 'tail -n \+1 -F /tmp/gateway.log' 2>/dev/null || true
pkill -f 'ssh.*openshell-' 2>/dev/null || true
sleep 2
# Final snapshot: tail -F glob expands once at start, so log files
# for openclaw processes that ran as a different UID (creating new
# /tmp/openclaw-<uid>/ dirs mid-test) get missed. Re-glob now and
# append every openclaw log file from each live sandbox to the
# per-sandbox docker-logs file.
#
# Use `nemoclaw <name> logs` (not raw openshell ssh-config + ssh)
# because nemoclaw handles SSH key/host setup and is robust to
# streamer race conditions. Tested working in TC-SBX-04.