Skip to content

fix(e2e): fix 3 stale assertions in newly-wired nightly E2E tests - #2656

Merged
cv merged 5 commits into
mainfrom
fix/e2e-stale-assertions
Apr 29, 2026
Merged

fix(e2e): fix 3 stale assertions in newly-wired nightly E2E tests#2656
cv merged 5 commits into
mainfrom
fix/e2e-stale-assertions

Conversation

@jyaunches

@jyaunches jyaunches commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Three newly-wired nightly E2E tests fail due to stale assertions:

  1. double-onboard-e2e — grepped for "Reusing existing NemoClaw gateway" log message that no longer exists
  2. onboard-repair-e2e — relied on NEMOCLAW_POLICY_MODE=invalid causing exit 1; product now gracefully falls back
  3. onboard-resume-e2e — same as onboard-repair

Fixes

double-onboard: container ID instead of log grep

Replace the fragile log-text grep with a Docker container ID comparison. Capture the gateway container ID before and after each onboard, verify it's the same container.

onboard-repair & onboard-resume: simulate interrupted state

Instead of relying on an invalid policy mode to create a failure, run a successful onboard with NEMOCLAW_POLICY_MODE=skip, then patch the session file to simulate a failure at the policies step. This creates the exact resumable state the test needs without depending on specific product error behavior.

Philosophy

All three fixes follow the same principle: assert on observable state, not log text or exit codes that drift as the product evolves.

Summary by CodeRabbit

  • Tests
    • Enhanced end-to-end testing with improved gateway reuse validation that verifies container identity, replacing log message parsing for more reliable and accurate test results.

The test grepped for 'Reusing existing NemoClaw gateway' but the product
now prints '✓ Reusing existing gateway' (the 'NemoClaw' prefix was
dropped). Update both assertions to match the current output.

Fixes double-onboard-e2e nightly failures.
@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 75a1abfc-d741-4c81-b4fc-3565637b7562

📥 Commits

Reviewing files that changed from the base of the PR and between 9fb78d1 and 1019da9.

📒 Files selected for processing (1)
  • test/e2e/test-double-onboard.sh

📝 Walkthrough

Walkthrough

The test script updates gateway reuse validation by replacing log message substring matching with Docker container identity checks. The script now captures the openshell-cluster-nemoclaw container ID before and after each onboard operation, comparing them to verify the container remains unchanged, with updated pass/fail output strings displaying the observed container IDs.

Changes

Cohort / File(s) Summary
Test Validation Method
test/e2e/test-double-onboard.sh
Replaces log-based gateway reuse validation (substring matching for "Reusing healthy NemoClaw gateway") with Docker container ID snapshot comparisons, verifying the container identity remains unchanged across onboard operations.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A rabbit hops through testing trails,
Where Docker IDs now tell the tales,
No log-string searches here, you see—
Just snapshot IDs, one and three!
Gateway reuse? Now verified with care,
Container unchanged—a robust affair! 🐳

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(e2e): fix 3 stale assertions in newly-wired nightly E2E tests' directly and clearly summarizes the main objective of the changeset—fixing three stale assertions in E2E tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/e2e-stale-assertions

Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 25088874456
Branch: fix/e2e-stale-assertions
Requested jobs: double-onboard-e2e,onboard-repair-e2e,onboard-resume-e2e
Summary: 0 passed, 0 failed, 22 skipped

Job Result
cloud-e2e ⏭️ skipped
cloud-inference-e2e ⏭️ skipped
cloud-onboard-e2e ⏭️ skipped
deployment-services-e2e ⏭️ skipped
diagnostics-e2e ⏭️ skipped
docs-validation-e2e ⏭️ skipped
gpu-e2e ⏭️ skipped
hermes-e2e ⏭️ skipped
inference-routing-e2e ⏭️ skipped
messaging-providers-e2e ⏭️ skipped
network-policy-e2e ⏭️ skipped
overlayfs-autofix-e2e ⏭️ skipped
rebuild-hermes-e2e ⏭️ skipped
rebuild-openclaw-e2e ⏭️ skipped
sandbox-operations-e2e ⏭️ skipped
sandbox-survival-e2e ⏭️ skipped
shields-config-e2e ⏭️ skipped
skill-agent-e2e ⏭️ skipped
skip-permissions-e2e ⏭️ skipped
snapshot-commands-e2e ⏭️ skipped
token-rotation-e2e ⏭️ skipped
upgrade-stale-sandbox-e2e ⏭️ skipped

Replace the fragile log-text grep with a Docker container ID comparison.
Capture the gateway container ID before and after each onboard, then
verify it's the same container. This asserts on observable state rather
than log messages that drift as the product evolves.
@jyaunches jyaunches changed the title fix(e2e): update stale gateway-reuse grep in double-onboard test fix(e2e): assert gateway reuse via container ID instead of log grep Apr 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/e2e/test-double-onboard.sh`:
- Line 277: The docker container name filter uses substring matching which can
return the wrong container; update the docker ps calls that use docker ps -qf
"name=openshell-cluster-nemoclaw" (e.g., the assignment to GATEWAY_ID_BEFORE and
the other three occurrences) to use an exact-name regex anchor: docker ps -qf
"name=^/openshell-cluster-nemoclaw$" so the filter matches the exact container
name only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: feac6e1e-8a58-4be5-b14e-e2190836b69e

📥 Commits

Reviewing files that changed from the base of the PR and between c6edcfc and 2b51511.

📒 Files selected for processing (1)
  • test/e2e/test-double-onboard.sh

section "Phase 3: Second onboard ($SANDBOX_A — same name, recreate)"
info "Running nemoclaw onboard with NEMOCLAW_RECREATE_SANDBOX=1..."

GATEWAY_ID_BEFORE=$(docker ps -qf "name=openshell-cluster-nemoclaw" | head -1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Broad filter (may include partial matches):"
docker ps --format '{{.ID}}\t{{.Names}}' --filter 'name=openshell-cluster-nemoclaw'

echo
echo "Exact filter (only exact container name):"
docker ps --format '{{.ID}}\t{{.Names}}' --filter 'name=^/openshell-cluster-nemoclaw$'

Repository: NVIDIA/NemoClaw

Length of output: 168


🏁 Script executed:

# First, check if the file exists and read the relevant sections
wc -l test/e2e/test-double-onboard.sh

Repository: NVIDIA/NemoClaw

Length of output: 95


🏁 Script executed:

# Read lines around 277 (the first occurrence mentioned)
sed -n '270,285p' test/e2e/test-double-onboard.sh

Repository: NVIDIA/NemoClaw

Length of output: 631


🏁 Script executed:

# Read lines around 320 and 332 (other occurrences)
sed -n '315,340p' test/e2e/test-double-onboard.sh

Repository: NVIDIA/NemoClaw

Length of output: 1057


🏁 Script executed:

# Check for SPDX header and shebang at the start of the file
head -5 test/e2e/test-double-onboard.sh

Repository: NVIDIA/NemoClaw

Length of output: 248


🏁 Script executed:

# Check if file is executable
ls -la test/e2e/test-double-onboard.sh

Repository: NVIDIA/NemoClaw

Length of output: 141


🌐 Web query:

Docker ps filter name exact match vs partial match documentation

💡 Result:

The docker ps (or docker container ls) command with --filter name=VALUE (or -f name=VALUE) performs a partial substring match by default, not an exact match. This is explicitly stated in the official Docker documentation. For partial match: - docker ps --filter "name=nostalgic" matches containers like i_am_nostalgic, nostalgic_stallman, and nostalgic_shockley. For exact match: - Use regex anchors: docker ps --filter "name=^exactname$" to match only containers named precisely "exactname". The ^ anchors to the start and $ to the end of the name string. This behavior is consistent across Docker CLI reference pages and manpages. Note that container names are unique, but the filter matches substrings unless regex is used. There is no built-in exact-match flag without regex; workarounds like piping to grep exist but are not native to the filter.

Citations:


🏁 Script executed:

# Check lines 289-294
sed -n '285,300p' test/e2e/test-double-onboard.sh

Repository: NVIDIA/NemoClaw

Length of output: 646


🏁 Script executed:

# Find all instances of the problematic docker ps command in the file
rg 'docker ps.*openshell-cluster-nemoclaw' test/e2e/test-double-onboard.sh -n

Repository: NVIDIA/NemoClaw

Length of output: 391


Use exact Docker name filtering to prevent nondeterministic container ID selection.

The docker ps -qf "name=openshell-cluster-nemoclaw" filter on lines 277, 289, 320, and 332 performs partial substring matching by default. Combined with | head -1, this can select the wrong container if similarly named containers exist (e.g., "openshell-cluster-nemoclaw-backup"), causing flaky assertions.

Use regex anchors to match the exact container name:

Proposed fix
+gateway_container_id() {
+  docker ps --filter "name=^openshell-cluster-nemoclaw$" --format '{{.ID}}'
+}
+
-GATEWAY_ID_BEFORE=$(docker ps -qf "name=openshell-cluster-nemoclaw" | head -1)
+GATEWAY_ID_BEFORE="$(gateway_container_id)"
@@
-GATEWAY_ID_AFTER=$(docker ps -qf "name=openshell-cluster-nemoclaw" | head -1)
+GATEWAY_ID_AFTER="$(gateway_container_id)"
@@
-GATEWAY_ID_BEFORE3=$(docker ps -qf "name=openshell-cluster-nemoclaw" | head -1)
+GATEWAY_ID_BEFORE3="$(gateway_container_id)"
@@
-GATEWAY_ID_AFTER3=$(docker ps -qf "name=openshell-cluster-nemoclaw" | head -1)
+GATEWAY_ID_AFTER3="$(gateway_container_id)"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/test-double-onboard.sh` at line 277, The docker container name
filter uses substring matching which can return the wrong container; update the
docker ps calls that use docker ps -qf "name=openshell-cluster-nemoclaw" (e.g.,
the assignment to GATEWAY_ID_BEFORE and the other three occurrences) to use an
exact-name regex anchor: docker ps -qf "name=^/openshell-cluster-nemoclaw$" so
the filter matches the exact container name only.

@github-actions

Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 25089704017
Branch: fix/e2e-stale-assertions
Requested jobs: double-onboard-e2e
Summary: 0 passed, 0 failed, 22 skipped

Job Result
cloud-e2e ⏭️ skipped
cloud-inference-e2e ⏭️ skipped
cloud-onboard-e2e ⏭️ skipped
deployment-services-e2e ⏭️ skipped
diagnostics-e2e ⏭️ skipped
docs-validation-e2e ⏭️ skipped
gpu-e2e ⏭️ skipped
hermes-e2e ⏭️ skipped
inference-routing-e2e ⏭️ skipped
messaging-providers-e2e ⏭️ skipped
network-policy-e2e ⏭️ skipped
overlayfs-autofix-e2e ⏭️ skipped
rebuild-hermes-e2e ⏭️ skipped
rebuild-openclaw-e2e ⏭️ skipped
sandbox-operations-e2e ⏭️ skipped
sandbox-survival-e2e ⏭️ skipped
shields-config-e2e ⏭️ skipped
skill-agent-e2e ⏭️ skipped
skip-permissions-e2e ⏭️ skipped
snapshot-commands-e2e ⏭️ skipped
token-rotation-e2e ⏭️ skipped
upgrade-stale-sandbox-e2e ⏭️ skipped

…e exit code

The onboard-repair test relied on NEMOCLAW_POLICY_MODE=invalid causing
exit 1 to create resumable state. The product now gracefully falls back
to suggested presets instead of failing, so the test never got its
interrupted state.

Fix: run a successful onboard with NEMOCLAW_POLICY_MODE=skip, then
patch the session file to look like it failed at the policies step.
This tests the same resume-repair logic without depending on a specific
product error behavior.
@github-actions

Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 25090094139
Branch: fix/e2e-stale-assertions
Requested jobs: double-onboard-e2e,onboard-repair-e2e
Summary: 0 passed, 0 failed, 22 skipped

Job Result
cloud-e2e ⏭️ skipped
cloud-inference-e2e ⏭️ skipped
cloud-onboard-e2e ⏭️ skipped
deployment-services-e2e ⏭️ skipped
diagnostics-e2e ⏭️ skipped
docs-validation-e2e ⏭️ skipped
gpu-e2e ⏭️ skipped
hermes-e2e ⏭️ skipped
inference-routing-e2e ⏭️ skipped
messaging-providers-e2e ⏭️ skipped
network-policy-e2e ⏭️ skipped
overlayfs-autofix-e2e ⏭️ skipped
rebuild-hermes-e2e ⏭️ skipped
rebuild-openclaw-e2e ⏭️ skipped
sandbox-operations-e2e ⏭️ skipped
sandbox-survival-e2e ⏭️ skipped
shields-config-e2e ⏭️ skipped
skill-agent-e2e ⏭️ skipped
skip-permissions-e2e ⏭️ skipped
snapshot-commands-e2e ⏭️ skipped
token-rotation-e2e ⏭️ skipped
upgrade-stale-sandbox-e2e ⏭️ skipped

Same approach as the onboard-repair fix: run a successful onboard with
NEMOCLAW_POLICY_MODE=skip, then patch the session file to simulate a
failure at the policies step.
@jyaunches jyaunches changed the title fix(e2e): assert gateway reuse via container ID instead of log grep fix(e2e): fix 3 stale assertions in newly-wired nightly E2E tests Apr 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
test/e2e/test-onboard-resume.sh (1)

189-194: Align the synthetic failed session with production failure fields

The patched object at Line 191 is missing recordedAt, and Lines 192-194 don’t set the failed step’s error/completedAt shape. Adding these fields will better match real failed-session state and reduce fragility.

Proposed refactor
 const data = JSON.parse(fs.readFileSync(file, "utf8"));
+const now = new Date().toISOString();
 data.status = "failed";
 data.lastCompletedStep = "openclaw";
-data.failure = { step: "policies", message: "simulated policy failure for E2E" };
+data.updatedAt = now;
+data.failure = {
+  step: "policies",
+  message: "simulated policy failure for E2E",
+  recordedAt: now,
+};
 if (data.steps && data.steps.policies) {
   data.steps.policies.status = "failed";
+  data.steps.policies.completedAt = null;
+  data.steps.policies.error = "simulated policy failure for E2E";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/test-onboard-resume.sh` around lines 189 - 194, The synthetic
failure object is missing production fields: when setting data.status = "failed"
and data.failure = { step: "policies", ... } add a recordedAt timestamp (e.g.
ISO string) to data.failure, and for the per-step object referenced by
data.steps.policies set the full failure shape by adding an error object (with
the message and any code/metadata) and a completedAt timestamp in addition to
status = "failed" so the synthetic session matches real failed-session structure
used by functions that read data.failure and data.steps.policies.
test/e2e/test-onboard-repair.sh (1)

169-174: Patch the simulated failure state to match production shape

At Line 171, the synthetic failure object omits recordedAt, and Lines 172-174 only flip steps.policies.status. Mirroring production failure writes more closely will make this test less brittle to session validation/normalization changes.

Proposed refactor
-const data = JSON.parse(fs.readFileSync(file, "utf8"));
+const data = JSON.parse(fs.readFileSync(file, "utf8"));
+const now = new Date().toISOString();
 data.status = "failed";
 data.lastCompletedStep = "openclaw";
-data.failure = { step: "policies", message: "simulated policy failure for E2E" };
+data.updatedAt = now;
+data.failure = {
+  step: "policies",
+  message: "simulated policy failure for E2E",
+  recordedAt: now,
+};
 if (data.steps && data.steps.policies) {
   data.steps.policies.status = "failed";
+  data.steps.policies.completedAt = null;
+  data.steps.policies.error = "simulated policy failure for E2E";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/test-onboard-repair.sh` around lines 169 - 174, The synthetic
failure state in the test mutates data.status, data.lastCompletedStep and
data.failure but omits production fields like recordedAt and may not mirror how
steps are written; update the test so data.failure includes recordedAt (e.g., a
timestamp string) alongside step and message, and ensure the steps mutation
mirrors production shape by either setting data.steps.policies = { status:
"failed", recordedAt: <timestamp> } or adding recordedAt to data.steps.policies
if it exists; keep mutations on data.status and data.lastCompletedStep as-is so
the failure shape matches production validation/normalization.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/e2e/test-onboard-repair.sh`:
- Around line 164-180: The current session-patching block runs the inline node
command but always calls pass "Session file patched..." even if the node command
fails, which hides failures; update the block that uses the inline node -e
script and SESSION_FILE so you check the node command's exit status (or use set
-e) and call fail "Session file patching failed" on error instead of pass, and
also verify the session file exists and is writable after the patch before
calling pass; reference the node -e invocation, SESSION_FILE variable, and the
pass/fail helper calls to locate and update the logic.

In `@test/e2e/test-onboard-resume.sh`:
- Around line 177-197: The test currently continues after a missing session file
and always prints "Session file patched" even if the node patch step fails;
update the script so the SESSION_FILE existence check halts the test on failure
(call fail/exit when the file is missing instead of merely logging), and run the
node patch step guarded—detect the node command exit status and call fail/exit
if it fails before emitting the pass "Session file patched to simulate
interrupted state"; reference the SESSION_FILE variable and the node -e patch
invocation to locate and update the checks and pass/fail calls.

---

Nitpick comments:
In `@test/e2e/test-onboard-repair.sh`:
- Around line 169-174: The synthetic failure state in the test mutates
data.status, data.lastCompletedStep and data.failure but omits production fields
like recordedAt and may not mirror how steps are written; update the test so
data.failure includes recordedAt (e.g., a timestamp string) alongside step and
message, and ensure the steps mutation mirrors production shape by either
setting data.steps.policies = { status: "failed", recordedAt: <timestamp> } or
adding recordedAt to data.steps.policies if it exists; keep mutations on
data.status and data.lastCompletedStep as-is so the failure shape matches
production validation/normalization.

In `@test/e2e/test-onboard-resume.sh`:
- Around line 189-194: The synthetic failure object is missing production
fields: when setting data.status = "failed" and data.failure = { step:
"policies", ... } add a recordedAt timestamp (e.g. ISO string) to data.failure,
and for the per-step object referenced by data.steps.policies set the full
failure shape by adding an error object (with the message and any code/metadata)
and a completedAt timestamp in addition to status = "failed" so the synthetic
session matches real failed-session structure used by functions that read
data.failure and data.steps.policies.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a8281d28-ebd4-4fb3-98d0-054da1d81d0a

📥 Commits

Reviewing files that changed from the base of the PR and between 2b51511 and 9fb78d1.

📒 Files selected for processing (2)
  • test/e2e/test-onboard-repair.sh
  • test/e2e/test-onboard-resume.sh

Comment thread test/e2e/test-onboard-repair.sh Outdated
Comment thread test/e2e/test-onboard-resume.sh Outdated
@cv
cv merged commit 7c5174e into main Apr 29, 2026
15 checks passed
DemianHeyGen pushed a commit to DemianHeyGen/NemoClaw that referenced this pull request Apr 30, 2026
…IDIA#2656)

## Problem

Three newly-wired nightly E2E tests fail due to stale assertions:

1. **`double-onboard-e2e`** — grepped for `"Reusing existing NemoClaw
gateway"` log message that no longer exists
2. **`onboard-repair-e2e`** — relied on `NEMOCLAW_POLICY_MODE=invalid`
causing exit 1; product now gracefully falls back
3. **`onboard-resume-e2e`** — same as onboard-repair

## Fixes

### double-onboard: container ID instead of log grep
Replace the fragile log-text grep with a Docker container ID comparison.
Capture the gateway container ID before and after each onboard, verify
it's the same container.

### onboard-repair & onboard-resume: simulate interrupted state
Instead of relying on an invalid policy mode to create a failure, run a
successful onboard with `NEMOCLAW_POLICY_MODE=skip`, then patch the
session file to simulate a failure at the policies step. This creates
the exact resumable state the test needs without depending on specific
product error behavior.

## Philosophy

All three fixes follow the same principle: **assert on observable state,
not log text or exit codes that drift as the product evolves.**

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Enhanced end-to-end testing with improved gateway reuse validation
that verifies container identity, replacing log message parsing for more
reliable and accurate test results.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Carlos Villela <cvillela@nvidia.com>
@wscurran wscurran added area: ci CI workflows, checks, release automation, or GitHub Actions area: e2e End-to-end tests, nightly failures, or validation infrastructure bug-fix PR fixes a bug or regression chore Build, CI, dependency, or tooling maintenance and removed CI/CD chore Build, CI, dependency, or tooling maintenance labels Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ci CI workflows, checks, release automation, or GitHub Actions area: e2e End-to-end tests, nightly failures, or validation infrastructure bug-fix PR fixes a bug or regression

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants