Skip to content

security: use argv for host-side helper calls#2476

Open
WilliamK112 wants to merge 1 commit intoNVIDIA:mainfrom
WilliamK112:security/argv-callers
Open

security: use argv for host-side helper calls#2476
WilliamK112 wants to merge 1 commit intoNVIDIA:mainfrom
WilliamK112:security/argv-callers

Conversation

@WilliamK112
Copy link
Copy Markdown
Contributor

@WilliamK112 WilliamK112 commented Apr 25, 2026

Summary

This is a small Phase 1 pass on #1889.

It converts shell-string helper calls to argv-style execution where the command does not actually need a shell, while intentionally leaving explicit shell-dependent cases in place, such as:

  • pipelines
  • shell builtins / shell environment composition
  • detached background starts that currently rely on shell redirection

What changed

  • switch host-side probe commands in src/lib/preflight.ts to argv form
  • switch Docker helper calls in src/lib/onboard.ts to argv form
  • replace the gateway volume cleanup pipe with explicit list + remove steps
  • add focused regression tests covering the new argv callsites

Validation

  • npm run build:cli
  • npm test -- test/argv-callers.test.ts

Notes

I also checked the broader test suite locally, but there are existing unrelated failures/noise on current main, so I kept validation scoped to the touched paths for this PR.

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability and safety of system probes and cleanup operations (preflight checks, host and container diagnostics, network/gateway discovery, and volume/port cleanup), reducing flaky failures and making lifecycle tasks more consistent.
  • Tests

    • Added end-to-end test coverage verifying command execution flows for host assessment, Docker/network probes, DNS/container diagnostics, and gateway state checks.

@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented Apr 25, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

📝 Walkthrough

Walkthrough

This change replaces shell-string command invocations with argv-array calls across onboarding and preflight code, introduces a CaptureCommand type, updates related interfaces/signatures, and adds tests that verify argv-based command usage in compiled modules.

Changes

Cohort / File(s) Summary
Onboard CLI & helpers
src/lib/onboard.ts
Replaces shell pipelines and shell-quoted command strings with argv-array invocations for Docker, ps/kill, which/hostname, and related helpers; preserves ignoreError behavior and continues best-effort volume deletion.
Preflight checks & types
src/lib/preflight.ts
Adds CaptureCommand type; changes AssessHostOpts.runCaptureImpl, ProbeContainerDnsOpts and related defaults to accept argv arrays; converts many probe commands (which, df parsing, systemctl, grep/tee swap persistence, docker/network queries) to argv-style calls and adjusts parsing logic.
Tests
test/argv-callers.test.ts
New Vitest suite that loads compiled modules and asserts key functions invoke external commands using argv arrays (captures run/runCapture calls), and validates parsing/returned values from mocked outputs.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through strings of shell and sigh,
Found arrays where tangled pipelines lie.
I nudged each call to pass args straight,
Tests now nod: the commands behave great.
A tiny rabbit cheers, clean and spry 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'security: use argv for host-side helper calls' clearly and concisely summarizes the main change: converting shell-string command execution to safer argv-style execution for host-side helper calls across preflight.ts and onboard.ts.
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 unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/lib/onboard.ts (1)

2598-2609: Extract gateway-volume cleanup into a shared helper.

This argv-based cleanup looks good, but the orphaned-container path later in the same file still carries a separate shell-pipeline implementation for the same volume removal. Keeping both versions around makes the security fix easy to miss the next time this logic changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/onboard.ts` around lines 2598 - 2609, Extract the duplicated Docker
volume cleanup into a shared helper (e.g., create a function
removeGatewayVolumes(gatewayName: string)) that uses
runCapture(["docker","volume","ls","-q","--filter",
`name=openshell-cluster-${gatewayName}`], {ignoreError:true}) to compute
volumeIds, then calls run(["docker","volume","rm", ...volumeIds],
{ignoreError:true, suppressOutput:true}) if any exist; replace the inline logic
that uses runCapture/run at the volume cleanup site and the separate
orphaned-container pipeline that performs the same removal to call
removeGatewayVolumes(GATEWAY_NAME) instead so both paths share one
implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/preflight.ts`:
- Around line 715-724: The current grep call in existingFstabEntry uses a plain
substring search which can match commented lines or unrelated paths; update the
check so it only matches active (non-commented) fstab entries for /swapfile by
changing the command invoked by run([...]) to use an anchored regex (e.g., grep
-qE with a pattern like '^[[:space:]]*[^#].*/swapfile\b') so only uncommented
lines containing /swapfile are detected; keep the subsequent runCapture([...
"sudo", "tee", "-a", "/etc/fstab"]) logic unchanged so the append happens only
when no active entry exists.

In `@test/argv-callers.test.ts`:
- Line 1: Add the required SPDX header lines to the top of this test file (above
the existing import { afterEach, describe, expect, it, vi } from "vitest";
line): insert the exact comment lines "// SPDX-FileCopyrightText: Copyright (c)
2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved." and "//
SPDX-License-Identifier: Apache-2.0" as the first two lines of the file.
- Around line 3-123: This test uses CommonJS require/require.cache which breaks
ESM test runner—replace require.resolve/require/require.cache usage with ESM
dynamic imports and Vitest module-reset/mocking: use string module specifiers
(e.g. const preflightPath = "../dist/lib/preflight.js") and await
import(preflightPath) to load assessHost, probeContainerDns,
getDockerBridgeGatewayIp, and onboard.getGatewayClusterContainerState, call
vi.resetModules() (or vi.restoreAllMocks() + vi.resetModules()) in afterEach
instead of deleting require.cache, and replace manual cache injection for runner
(where you override runCapture) with vi.mock(runnerPath, async () => ({
...(await vi.importActual(runnerPath)), runCapture: vi.fn(() => "running
healthy\n") })) so the tests use Vitest’s ESM-aware mocking and module
lifecycle.

---

Nitpick comments:
In `@src/lib/onboard.ts`:
- Around line 2598-2609: Extract the duplicated Docker volume cleanup into a
shared helper (e.g., create a function removeGatewayVolumes(gatewayName:
string)) that uses runCapture(["docker","volume","ls","-q","--filter",
`name=openshell-cluster-${gatewayName}`], {ignoreError:true}) to compute
volumeIds, then calls run(["docker","volume","rm", ...volumeIds],
{ignoreError:true, suppressOutput:true}) if any exist; replace the inline logic
that uses runCapture/run at the volume cleanup site and the separate
orphaned-container pipeline that performs the same removal to call
removeGatewayVolumes(GATEWAY_NAME) instead so both paths share one
implementation.
🪄 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: 4cd66351-0ab1-47ac-9975-34f03d2f7bbc

📥 Commits

Reviewing files that changed from the base of the PR and between 9c5a42a and 2f20e7d.

📒 Files selected for processing (3)
  • src/lib/onboard.ts
  • src/lib/preflight.ts
  • test/argv-callers.test.ts

Comment thread src/lib/preflight.ts
Comment on lines +715 to +724
const existingFstabEntry =
run(["grep", "-q", "/swapfile", "/etc/fstab"], {
ignoreError: true,
suppressOutput: true,
}).status === 0;
if (!existingFstabEntry) {
runCapture(["sudo", "tee", "-a", "/etc/fstab"], {
ignoreError: false,
input: "/swapfile none swap sw 0 0\n",
});
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

Match only active /swapfile entries in /etc/fstab.

Line 716 uses a plain substring grep, so a commented entry or an unrelated path containing /swapfile will suppress the append. The swap file still works for the current boot, but it will not be re-enabled after reboot.

Suggested fix
     const existingFstabEntry =
-      run(["grep", "-q", "/swapfile", "/etc/fstab"], {
+      run(["grep", "-qE", "^[[:space:]]*/swapfile[[:space:]]", "/etc/fstab"], {
         ignoreError: true,
         suppressOutput: true,
       }).status === 0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const existingFstabEntry =
run(["grep", "-q", "/swapfile", "/etc/fstab"], {
ignoreError: true,
suppressOutput: true,
}).status === 0;
if (!existingFstabEntry) {
runCapture(["sudo", "tee", "-a", "/etc/fstab"], {
ignoreError: false,
input: "/swapfile none swap sw 0 0\n",
});
const existingFstabEntry =
run(["grep", "-qE", "^[[:space:]]*/swapfile[[:space:]]", "/etc/fstab"], {
ignoreError: true,
suppressOutput: true,
}).status === 0;
if (!existingFstabEntry) {
runCapture(["sudo", "tee", "-a", "/etc/fstab"], {
ignoreError: false,
input: "/swapfile none swap sw 0 0\n",
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/preflight.ts` around lines 715 - 724, The current grep call in
existingFstabEntry uses a plain substring search which can match commented lines
or unrelated paths; update the check so it only matches active (non-commented)
fstab entries for /swapfile by changing the command invoked by run([...]) to use
an anchored regex (e.g., grep -qE with a pattern like
'^[[:space:]]*[^#].*/swapfile\b') so only uncommented lines containing /swapfile
are detected; keep the subsequent runCapture([... "sudo", "tee", "-a",
"/etc/fstab"]) logic unchanged so the append happens only when no active entry
exists.

Comment thread test/argv-callers.test.ts
@@ -0,0 +1,123 @@
import { afterEach, describe, expect, it, vi } from "vitest";
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 | 🟠 Major

Add the required SPDX header to this new test file.

This file is missing the repository-mandated license header. As per coding guidelines, **/*.{js,mjs,ts,tsx,sh,md}: Every source file must include an SPDX license header: // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. and // SPDX-License-Identifier: Apache-2.0.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/argv-callers.test.ts` at line 1, Add the required SPDX header lines to
the top of this test file (above the existing import { afterEach, describe,
expect, it, vi } from "vitest"; line): insert the exact comment lines "//
SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All
rights reserved." and "// SPDX-License-Identifier: Apache-2.0" as the first two
lines of the file.

Comment thread test/argv-callers.test.ts
Comment on lines +3 to +123
const preflightPath = require.resolve("../dist/lib/preflight.js");
const onboardPath = require.resolve("../dist/lib/onboard.js");
const runnerPath = require.resolve("../dist/lib/runner.js");

afterEach(() => {
vi.restoreAllMocks();
delete require.cache[preflightPath];
delete require.cache[onboardPath];
delete require.cache[runnerPath];
});

describe("argv callsites", () => {
it("assessHost uses argv commands for host probes", () => {
const seen: Array<string | readonly string[]> = [];
delete require.cache[preflightPath];
const preflight = require(preflightPath);

const assessment = preflight.assessHost({
platform: "linux",
env: {},
release: "6.6.0",
procVersion: "Linux version 6.6.0",
readFileImpl: () => {
throw new Error("no daemon config");
},
runCaptureImpl: (command: string | readonly string[], options?: { ignoreError?: boolean }) => {
seen.push(command);
const key = Array.isArray(command) ? command.join(" ") : command;
if (key === "which docker") return "/usr/bin/docker\n";
if (key === "which node") return "/usr/bin/node\n";
if (key === "which openshell") return "/usr/bin/openshell\n";
if (key === "which nvidia-smi") return "";
if (key === "which apt-get") return "/usr/bin/apt-get\n";
if (key === "which systemctl") return "/usr/bin/systemctl\n";
if (key === "docker info --format {{json .}}") return '{"ServerVersion":"27.0.0","OperatingSystem":"Docker Engine"}';
if (key === "systemctl is-active docker") return "active\n";
if (key === "systemctl is-enabled docker") return "enabled\n";
return options?.ignoreError ? "" : "";
},
});

expect(assessment.dockerInstalled).toBe(true);
expect(assessment.nodeInstalled).toBe(true);
expect(assessment.openshellInstalled).toBe(true);
expect(assessment.packageManager).toBe("apt");
expect(assessment.dockerReachable).toBe(true);
expect(seen).toContainEqual(["which", "docker"]);
expect(seen).toContainEqual(["docker", "info", "--format", "{{json .}}"]);
expect(seen).toContainEqual(["systemctl", "is-active", "docker"]);
expect(seen).toContainEqual(["systemctl", "is-enabled", "docker"]);
expect(seen.some((command) => typeof command === "string" && command.includes("command -v"))).toBe(false);
});

it("probeContainerDns defaults to argv docker run", () => {
const seen: Array<string | readonly string[]> = [];
delete require.cache[preflightPath];
const { probeContainerDns } = require(preflightPath);

const result = probeContainerDns({
runCaptureImpl: (command: string | readonly string[], opts?: { ignoreError?: boolean; timeout?: number }) => {
seen.push(command);
expect(opts?.ignoreError).toBe(true);
expect(opts?.timeout).toBe(20_000);
return "Server:\t1.1.1.1\nName:\tregistry.npmjs.org\nAddress: 104.16.25.35\n";
},
});

expect(result).toEqual({ ok: true });
expect(seen).toEqual([
["docker", "run", "--rm", "--pull=missing", "busybox:latest", "nslookup", "registry.npmjs.org"],
]);
});

it("getDockerBridgeGatewayIp uses argv docker inspect", () => {
delete require.cache[preflightPath];
const { getDockerBridgeGatewayIp } = require(preflightPath);
const seen: Array<string | readonly string[]> = [];

const gateway = getDockerBridgeGatewayIp((command: string | readonly string[]) => {
seen.push(command);
return "172.17.0.1fd00:abcd::1\n";
});

expect(gateway).toBe("172.17.0.1");
expect(seen).toEqual([
["docker", "network", "inspect", "bridge", "--format", "{{range .IPAM.Config}}{{.Gateway}}{{end}}"],
]);
});

it("getGatewayClusterContainerState uses argv docker inspect", () => {
const actualRunner = require(runnerPath);
const runCapture = vi.fn(() => "running healthy\n");
require.cache[runnerPath] = {
id: runnerPath,
filename: runnerPath,
loaded: true,
exports: {
...actualRunner,
runCapture,
},
} as any;

delete require.cache[onboardPath];
const onboard = require(onboardPath);
const state = onboard.getGatewayClusterContainerState();

expect(state).toBe("running healthy");
expect(runCapture).toHaveBeenCalledWith(
[
"docker",
"inspect",
"--type",
"container",
"--format",
"{{.State.Status}}{{if .State.Health}} {{.State.Health.Status}}{{end}}",
"openshell-cluster-nemoclaw",
],
{ ignoreError: true },
);
});
});
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's check if the file exists and examine its contents
if [ -f "test/argv-callers.test.ts" ]; then
    echo "=== File Found ==="
    wc -l test/argv-callers.test.ts
    echo ""
    echo "=== First 50 lines ==="
    head -50 test/argv-callers.test.ts
else
    echo "File test/argv-callers.test.ts not found"
fi

Repository: NVIDIA/NemoClaw

Length of output: 2326


🏁 Script executed:

#!/bin/bash
# Check the vitest configuration and ESM setup
echo "=== Checking vitest.config files ==="
find . -maxdepth 2 -name "vitest.config.*" -o -name "vite.config.*" | head -10
echo ""
echo "=== Checking package.json for module type ==="
if [ -f "package.json" ]; then
    cat package.json | head -30
fi

Repository: NVIDIA/NemoClaw

Length of output: 2016


🏁 Script executed:

#!/bin/bash
# Check if there's a tsconfig.json to understand module system
echo "=== Checking tsconfig.json ==="
if [ -f "tsconfig.json" ]; then
    cat tsconfig.json | jq . 2>/dev/null || cat tsconfig.json
fi

Repository: NVIDIA/NemoClaw

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Check the vitest configuration
echo "=== vitest.config.ts ==="
cat vitest.config.ts
echo ""
echo "=== Check for 'type' field in package.json ==="
jq '.type' package.json 2>/dev/null || grep '"type"' package.json || echo "No 'type' field found"

Repository: NVIDIA/NemoClaw

Length of output: 1879


🏁 Script executed:

#!/bin/bash
# Check if there are other test files using require pattern
echo "=== Other test files using require ==="
find test -name "*.test.ts" -o -name "*.test.js" | while read file; do
  if grep -l "require(" "$file" 2>/dev/null; then
    echo "File: $file"
  fi
done
echo ""
echo "=== Check how many test files exist ==="
find test -name "*.test.ts" -o -name "*.test.js" | wc -l

Repository: NVIDIA/NemoClaw

Length of output: 2166


Convert this suite to ESM module loading.

test/ directory requires ESM imports per coding guidelines, but this file uses CommonJS (require.resolve(), require(), require.cache). In an ESM setup, these globals are unavailable and this approach bypasses Vitest's vi.resetModules() flow. Use dynamic await import(...) with Vitest mocks instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/argv-callers.test.ts` around lines 3 - 123, This test uses CommonJS
require/require.cache which breaks ESM test runner—replace
require.resolve/require/require.cache usage with ESM dynamic imports and Vitest
module-reset/mocking: use string module specifiers (e.g. const preflightPath =
"../dist/lib/preflight.js") and await import(preflightPath) to load assessHost,
probeContainerDns, getDockerBridgeGatewayIp, and
onboard.getGatewayClusterContainerState, call vi.resetModules() (or
vi.restoreAllMocks() + vi.resetModules()) in afterEach instead of deleting
require.cache, and replace manual cache injection for runner (where you override
runCapture) with vi.mock(runnerPath, async () => ({ ...(await
vi.importActual(runnerPath)), runCapture: vi.fn(() => "running healthy\n") }))
so the tests use Vitest’s ESM-aware mocking and module lifecycle.

@WilliamK112 WilliamK112 force-pushed the security/argv-callers branch from 2f20e7d to 69dc6a9 Compare April 25, 2026 05:43
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (3)
src/lib/preflight.ts (1)

715-725: ⚠️ Potential issue | 🟡 Minor

Tighten the /swapfile match before skipping the append.

Line 716 still uses a plain substring grep, so a commented entry or an unrelated path containing /swapfile will make existingFstabEntry look true. In that case the swapfile works for the current boot, but it will not persist across reboot.

Suggested fix
     const existingFstabEntry =
-      run(["grep", "-q", "/swapfile", "/etc/fstab"], {
+      run(["grep", "-qE", "^[[:space:]]*/swapfile[[:space:]]", "/etc/fstab"], {
         ignoreError: true,
         suppressOutput: true,
       }).status === 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/preflight.ts` around lines 715 - 725, The current check using
run(["grep", "-q", "/swapfile", "/etc/fstab"]) can be fooled by commented lines
or other paths containing "/swapfile"; change the existence test (the
existingFstabEntry assignment) to search for a non-commented fstab entry that
lists /swapfile as the swap file (e.g., a regex like
'^[[:space:]]*[^#].*\s+/swapfile(\s|$)' or equivalent) so only active fstab
entries match, then keep the append logic using
runCapture(["sudo","tee","-a","/etc/fstab"]) unchanged; update the grep
invocation inside run(...) that sets existingFstabEntry (and any related
variable) to use the stricter pattern.
test/argv-callers.test.ts (2)

3-123: ⚠️ Potential issue | 🟠 Major

Convert this suite from CommonJS loading/cache mutation to ESM + Vitest-native module control.

Lines 3-123 use require.resolve, require, and require.cache, which violates the test/ ESM rule and makes module lifecycle handling brittle. Use await import(...), vi.resetModules(), and vi.mock(...) instead.

Refactor sketch (ESM-compatible)
-const preflightPath = require.resolve("../dist/lib/preflight.js");
-const onboardPath = require.resolve("../dist/lib/onboard.js");
-const runnerPath = require.resolve("../dist/lib/runner.js");
+const preflightPath = "../dist/lib/preflight.js";
+const onboardPath = "../dist/lib/onboard.js";
+const runnerPath = "../dist/lib/runner.js";

 afterEach(() => {
   vi.restoreAllMocks();
-  delete require.cache[preflightPath];
-  delete require.cache[onboardPath];
-  delete require.cache[runnerPath];
+  vi.resetModules();
 });

-it("assessHost uses argv commands for host probes", () => {
+it("assessHost uses argv commands for host probes", async () => {
   const seen: Array<string | readonly string[]> = [];
-  delete require.cache[preflightPath];
-  const preflight = require(preflightPath);
+  const preflight = await import(preflightPath);
  ...
 });

-it("probeContainerDns defaults to argv docker run", () => {
+it("probeContainerDns defaults to argv docker run", async () => {
   const seen: Array<string | readonly string[]> = [];
-  delete require.cache[preflightPath];
-  const { probeContainerDns } = require(preflightPath);
+  const { probeContainerDns } = await import(preflightPath);
  ...
 });

-it("getDockerBridgeGatewayIp uses argv docker inspect", () => {
-  delete require.cache[preflightPath];
-  const { getDockerBridgeGatewayIp } = require(preflightPath);
+it("getDockerBridgeGatewayIp uses argv docker inspect", async () => {
+  const { getDockerBridgeGatewayIp } = await import(preflightPath);
  ...
 });

-it("getGatewayClusterContainerState uses argv docker inspect", () => {
-  const actualRunner = require(runnerPath);
+it("getGatewayClusterContainerState uses argv docker inspect", async () => {
   const runCapture = vi.fn(() => "running healthy\n");
-  require.cache[runnerPath] = { ... } as any;
+  vi.mock(runnerPath, async () => {
+    const actualRunner = await vi.importActual<Record<string, unknown>>(runnerPath);
+    return { ...actualRunner, runCapture };
+  });

-  delete require.cache[onboardPath];
-  const onboard = require(onboardPath);
+  const onboard = await import(onboardPath);
   const state = onboard.getGatewayClusterContainerState();
  ...
 });
#!/bin/bash
# Verify CommonJS-only patterns are still present in this test file.
rg -n -C2 'require\.resolve\(|\brequire\(|require\.cache' test/argv-callers.test.ts

As per coding guidelines, test/**/*.{js,ts}: test/ directory must use ESM (import/export) for test files and test/**/*.test.{js,ts}: Root-level integration tests in test/ directory should use ESM imports and mock external dependencies without calling real NVIDIA APIs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/argv-callers.test.ts` around lines 3 - 123, The tests use CommonJS
require/require.cache and require.resolve (preflightPath/onboardPath/runnerPath)
which violates ESM test rules; replace all require.resolve/require/require.cache
mutations with dynamic ESM imports (await import(...)) and use Vitest module
helpers: call vi.resetModules() in afterEach, replace manual cache stubs with
vi.mock(...) to override runner.runCapture when testing
getGatewayClusterContainerState, and when importing preflight functions
(assessHost, probeContainerDns, getDockerBridgeGatewayIp) import them via await
import and pass mocked implementations (runCaptureImpl/readFileImpl) rather than
mutating require.cache; ensure probeContainerDns and getDockerBridgeGatewayIp
assertions still inspect the array commands and options, and remove any use of
delete require.cache and require.resolve.

1-1: ⚠️ Potential issue | 🟠 Major

Add the required SPDX header to this test file.

Line 1 starts directly with imports; the mandatory SPDX copyright and license lines are missing.

Proposed fix
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
 import { afterEach, describe, expect, it, vi } from "vitest";

As per coding guidelines, **/*.{js,mjs,ts,tsx,sh,md}: Every source file must include an SPDX license header: // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. and // SPDX-License-Identifier: Apache-2.0.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/argv-callers.test.ts` at line 1, This file is missing the mandatory SPDX
header; add the two required comment lines immediately above the first import
statement (the line starting with "import { afterEach, describe, expect, it, vi
}"): "// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION &
AFFILIATES. All rights reserved." and "// SPDX-License-Identifier: Apache-2.0",
ensuring they are the very first lines in the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/lib/preflight.ts`:
- Around line 715-725: The current check using run(["grep", "-q", "/swapfile",
"/etc/fstab"]) can be fooled by commented lines or other paths containing
"/swapfile"; change the existence test (the existingFstabEntry assignment) to
search for a non-commented fstab entry that lists /swapfile as the swap file
(e.g., a regex like '^[[:space:]]*[^#].*\s+/swapfile(\s|$)' or equivalent) so
only active fstab entries match, then keep the append logic using
runCapture(["sudo","tee","-a","/etc/fstab"]) unchanged; update the grep
invocation inside run(...) that sets existingFstabEntry (and any related
variable) to use the stricter pattern.

In `@test/argv-callers.test.ts`:
- Around line 3-123: The tests use CommonJS require/require.cache and
require.resolve (preflightPath/onboardPath/runnerPath) which violates ESM test
rules; replace all require.resolve/require/require.cache mutations with dynamic
ESM imports (await import(...)) and use Vitest module helpers: call
vi.resetModules() in afterEach, replace manual cache stubs with vi.mock(...) to
override runner.runCapture when testing getGatewayClusterContainerState, and
when importing preflight functions (assessHost, probeContainerDns,
getDockerBridgeGatewayIp) import them via await import and pass mocked
implementations (runCaptureImpl/readFileImpl) rather than mutating
require.cache; ensure probeContainerDns and getDockerBridgeGatewayIp assertions
still inspect the array commands and options, and remove any use of delete
require.cache and require.resolve.
- Line 1: This file is missing the mandatory SPDX header; add the two required
comment lines immediately above the first import statement (the line starting
with "import { afterEach, describe, expect, it, vi }"): "//
SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All
rights reserved." and "// SPDX-License-Identifier: Apache-2.0", ensuring they
are the very first lines in the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 16eda3cd-80f5-4977-ad92-1b99f1e7516d

📥 Commits

Reviewing files that changed from the base of the PR and between 2f20e7d and 69dc6a9.

📒 Files selected for processing (3)
  • src/lib/onboard.ts
  • src/lib/preflight.ts
  • test/argv-callers.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant