Skip to content

Commit c1ed614

Browse files
test(e2e): accept supervisor gateway recovery (#9090)
<!-- markdownlint-disable MD041 --> ## Summary The gateway-recovery live target now accepts either successful production recovery path after terminating the managed gateway. In [Actions run 31767577321](https://github.com/NVIDIA/NemoClaw/actions/runs/31767577321), PID 1 replaced the process before `connect --probe-only` completed, so the CLI correctly reported that the gateway was already running. The target rejected that result even though its deterministic CLI contract explicitly requires that output for an automatic supervisor replacement. The live target still requires a different process identity, the complete guard chain, healthy inference, and 15 seconds of process stability. ## Changes - Accept the controller-recovery and supervisor-recovery success messages. - Record which recovery path won the race in the live evidence summary. - Keep the existing process replacement, guard, inference, and stability assertions. - Rename the target phase and title to describe the behavior it proves. ## Type of Change - [x] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [ ] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Quality Gates - [x] Tests added or updated for changed behavior - [ ] Existing tests cover changed behavior — justification: - [ ] Tests not applicable — justification: - [ ] Docs updated for user-facing behavior changes - [x] Docs not applicable — justification: Production recovery behavior and CLI output do not change; this corrects one live E2E expectation. - [ ] Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging) - [ ] Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: - [ ] Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue: ## Documentation Writer Review - [x] Documentation writer subagent reviewed the completed changes - Result: `no-docs-needed` - Evidence: Existing command, recovery, lifecycle-control, and troubleshooting pages already describe supervisor relaunch and successful already-running recovery. The docs do not promise which recovery actor appears in the success line. - Agent: Codex documentation writer subagent <!-- docs-review-head-sha: f5cc320 --> <!-- docs-review-agents-blob-sha: e30afb2 --> ## DGX Station Hardware Evidence - [ ] Tested on DGX Station - Tested commit: Not applicable; DGX Station preparation is unchanged. - Station profile/scenario: Not applicable. - Result: Not applicable. - Supporting evidence: Not applicable. ## Verification - [x] PR description includes a `Signed-off-by:` line and every commit appears as `Verified` in GitHub - [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or `npm run validate:pr` passed after refreshing `origin/main` when hooks were skipped or unavailable - [x] Targeted behavior tests pass for the current change set, or tests are marked not applicable above — deterministic connect recovery: 2 passed; live target collection: passed - [x] Applicable broad gate passed — exact Vitest project membership, test-title style, targeted repository hooks, and diff validation passed - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, or credentials committed - [ ] `npm run docs` builds without warnings (doc changes only) - [ ] Doc pages follow the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md) (doc changes only) - [ ] New doc pages include SPDX header and frontmatter (new pages only) --- Signed-off-by: Apurv Kumaria <akumaria@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved crash-loop recovery coverage to accept recovery through either supported production path. * Recovery test results now record and report which path successfully restored service. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
1 parent 6e0df3f commit c1ed614

1 file changed

Lines changed: 21 additions & 8 deletions

File tree

test/e2e/live/issue-2478-crash-loop-recovery.test.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ async function runProbeOnly(
229229
},
230230
sandboxName: string,
231231
artifactName: string,
232-
): Promise<void> {
232+
): Promise<"connect" | "supervisor"> {
233233
const result = await host.nemoclaw([sandboxName, "connect", "--probe-only"], {
234234
artifactName,
235235
env: probeEnv(),
@@ -239,10 +239,18 @@ async function runProbeOnly(
239239
result.exitCode,
240240
`${artifactName} failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
241241
).toBe(0);
242+
const connectRecovery = `Probe complete: recovered OpenClaw gateway in '${sandboxName}'.`;
243+
const supervisorRecovery = `Probe complete: OpenClaw gateway is running in '${sandboxName}'.`;
244+
const recoveryPath = result.stdout.includes(connectRecovery)
245+
? "connect"
246+
: result.stdout.includes(supervisorRecovery)
247+
? "supervisor"
248+
: null;
242249
expect(
243-
result.stdout,
244-
`${artifactName} did not exercise connect-driven recovery\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
245-
).toContain(`Probe complete: recovered OpenClaw gateway in '${sandboxName}'.`);
250+
recoveryPath,
251+
`${artifactName} did not observe a healthy gateway after termination\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
252+
).not.toBeNull();
253+
return recoveryPath!;
246254
}
247255

248256
async function terminateGatewayIdentity(
@@ -301,13 +309,13 @@ function sleep(ms: number): Promise<void> {
301309
return new Promise((resolve) => setTimeout(resolve, ms));
302310
}
303311

304-
test("connect-driven gateway recovery restores the guard chain and keeps the recovered process identity for 15 seconds (#2478)", {
312+
test("gateway recovery restores the guard chain and keeps the recovered process identity for 15 seconds (#2478)", {
305313
meta: {
306314
e2ePhases: [
307315
"start the compatible endpoint and confirm host readiness",
308316
"onboard the guarded OpenClaw sandbox",
309317
"confirm initial gateway and inference health",
310-
"terminate one live gateway and recover it through the production connect path",
318+
"terminate one live gateway and verify production recovery",
311319
"verify the recovered process identity remains unchanged for 15 seconds",
312320
],
313321
},
@@ -354,14 +362,18 @@ test("connect-driven gateway recovery restores the guard chain and keeps the rec
354362
initialIdentity,
355363
);
356364

357-
progress.phase("terminate one live gateway and recover it through the production connect path");
365+
progress.phase("terminate one live gateway and verify production recovery");
358366
await terminateGatewayIdentity(
359367
sandbox,
360368
instance.sandboxName,
361369
preRecoveryIdentity!,
362370
"functional-recovery-terminate-gateway",
363371
);
364-
await runProbeOnly(host, instance.sandboxName, "functional-recovery-connect-probe-only");
372+
const recoveryPath = await runProbeOnly(
373+
host,
374+
instance.sandboxName,
375+
"functional-recovery-connect-probe-only",
376+
);
365377
const recoveredIdentity = await waitForGatewayIdentity(gateway, instance, 45_000);
366378
expect(
367379
recoveredIdentity,
@@ -386,6 +398,7 @@ test("connect-driven gateway recovery restores the guard chain and keeps the rec
386398
await artifacts.writeJson("functional-recovery-summary.json", {
387399
initialIdentity,
388400
preRecoveryIdentity,
401+
recoveryPath,
389402
recoveredIdentity,
390403
stableIdentity,
391404
stabilitySeconds: STABILITY_SECONDS,

0 commit comments

Comments
 (0)