Skip to content

refactor(cli): replace spawnSync("sleep") with native wait utility#2027

Merged
brandonpelfrey merged 16 commits intoNVIDIA:mainfrom
ksapru:refactor/native-sleep-utility
Apr 21, 2026
Merged

refactor(cli): replace spawnSync("sleep") with native wait utility#2027
brandonpelfrey merged 16 commits intoNVIDIA:mainfrom
ksapru:refactor/native-sleep-utility

Conversation

@ksapru
Copy link
Copy Markdown
Contributor

@ksapru ksapru commented Apr 17, 2026

Summary

This PR refactors the NemoClaw CLI to replace inefficient and platform-dependent spawnSync("sleep") subprocess calls with a native Node.js wait utility. By using Atomics.wait, we provide a reliable synchronous sleep mechanism that avoids the overhead of spawning new processes, improving performance and robustness during onboarding and deployment.

Related Issue

Phase #2001

Changes

  • NEW: src/lib/wait.ts providing sleepMs() and sleepSeconds() primitives.
  • REFAC: Swapped spawnSync("sleep") for native sleepSeconds() in src/nemoclaw.ts, src/lib/onboard.ts, src/lib/deploy.ts, src/lib/agent-onboard.ts, and src/lib/nim.ts.
  • TEST: Added test/wait.test.ts to verify timing accuracy and updated runner.test.ts.

Type of Change

  • 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)

Verification

  • npx prek run --all-files passes (Several pre-existing unrelated flakes detected in the full suite)
  • npm test passes (Verified focused suites: wait, onboard, cli, deploy, agent-onboard, and nim)
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

AI Disclosure

  • AI-assisted — tool: Antigravity

Signed-off-by: Krish Sapru ksapru@bu.edu

Summary by CodeRabbit

  • New Features

    • Added a shared blocking sleep utility for CLI usage to centralize timing behavior.
  • Refactor

    • Replaced external/shell-based delays with the shared timing utility, standardizing pauses across onboarding, deployment, monitoring, and recovery flows for more reliable retry and health-check behavior.
  • Tests

    • Added unit tests validating the timing utility’s durations and edge-case (zero/invalid) behavior.

@ksapru ksapru marked this pull request as ready for review April 17, 2026 17:10
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Centralizes synchronous blocking sleep into src/lib/wait.ts (exports sleepMs and sleepSeconds) and replaces subprocess-based sleep calls across multiple modules with sleepSeconds, preserving existing polling loops and control flow; adds tests for the new utilities.

Changes

Cohort / File(s) Summary
Sleep Utility Implementation
src/lib/wait.ts
Add sleepMs(ms) (uses SharedArrayBuffer + Atomics.wait) and sleepSeconds(seconds) (delegates to sleepMs).
Sleep Usage Migration
src/lib/agent-onboard.ts, src/lib/deploy.ts, src/lib/nim.ts, src/nemoclaw.ts
Replace synchronous subprocess sleep calls with sleepSeconds(...) from ./wait; preserve loops, timeouts, and output behavior.
Onboard Helper Update
src/lib/onboard.ts
Replace local sleep(seconds) implementation to delegate to sleepSeconds instead of spawning a subprocess.
Test Suite
test/wait.test.ts
Add Vitest tests verifying sleepMs/sleepSeconds block for expected durations (~100ms) and return immediately for zero/negative/non-finite inputs.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I nibbled at the ticking clock,

Swapped loud spawns for softer rock.
Buffers dream and atoms wait,
Quiet sleeps that feel just right.
Hops of code — a peaceful night.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main refactoring change: replacing subprocess-based sleep calls with a native Node.js wait utility across multiple modules.

✏️ 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: 4

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

14-15: Avoid per-call SharedArrayBuffer allocation in hot polling paths.

Reuse one module-level buffer to reduce allocation churn.

♻️ Proposed refactor
+const SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
+
 export function sleepMs(ms: number): void {
   if (!Number.isFinite(ms) || ms <= 0) return;
-  const buffer = new Int32Array(new SharedArrayBuffer(4));
-  Atomics.wait(buffer, 0, 0, ms);
+  Atomics.wait(SLEEP_BUFFER, 0, 0, ms);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/wait.ts` around lines 14 - 15, Replace the per-call allocation of
"buffer" (new Int32Array(new SharedArrayBuffer(4))) with a single module-level
Int32Array instance (e.g., const WAIT_BUFFER = new Int32Array(new
SharedArrayBuffer(4))) and have the wait function call Atomics.wait(WAIT_BUFFER,
0, 0, ms) instead of creating a new SharedArrayBuffer each invocation; update
references to "buffer" in the function to use WAIT_BUFFER so allocation churn is
removed while keeping Atomics.wait semantics.
🤖 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/deploy.ts`:
- Line 336: The module calls the helper sleepSeconds but never imports it,
causing type/runtime failures; fix by adding an import for sleepSeconds from the
wait module at the top of the deploy module (i.e., add an import statement for
sleepSeconds) so the calls to sleepSeconds() in this file resolve correctly and
TypeScript compiles.

In `@src/lib/onboard.ts`:
- Around line 54-56: The file still uses the local sleep() helper which invokes
spawnSync("sleep") instead of the imported sleepSeconds from ./wait; replace the
local spawnSync-based sleep implementation (and all its callers) to use the
exported sleepSeconds utility: remove or refactor the sleep() function to call
or return sleepSeconds(seconds) and make callers await sleepSeconds(...) (or
adapt to its promise-based API) so the native wait utility is actually used;
ensure any references to spawnSync("sleep") are removed and tests/flows that
relied on synchronous behavior are adjusted to the async Promise-based contract.

In `@src/lib/wait.ts`:
- Around line 12-16: sleepMs currently calls Atomics.wait with ms even when ms
is NaN or Infinity (which bypasses the ms <= 0 check and can block
indefinitely); update the guard in sleepMs to first verify the timeout is a
finite number (use Number.isFinite or equivalent) and only proceed to create the
SharedArrayBuffer and call Atomics.wait when ms is finite and greater than
0—otherwise return early (or reject) to avoid passing non-finite timeouts to
Atomics.wait; refer to the sleepMs function and the Atomics.wait/buffer usage to
locate and apply this change.

In `@test/wait.test.ts`:
- Around line 9-28: The tests for sleepMs and sleepSeconds use Date.now() and a
brittle upper bound of 200ms; switch to a monotonic timer (performance.now()) in
both tests and relax the upper bound to something CI-friendly (e.g., assert
duration >= 100 and duration < 500) so the assertions for functions sleepMs and
sleepSeconds are stable on loaded runners; update both test blocks ("sleepMs
blocks for approximately the requested time" and "sleepSeconds blocks for
approximately the requested time") to use performance.now() and the wider upper
bound.

---

Nitpick comments:
In `@src/lib/wait.ts`:
- Around line 14-15: Replace the per-call allocation of "buffer" (new
Int32Array(new SharedArrayBuffer(4))) with a single module-level Int32Array
instance (e.g., const WAIT_BUFFER = new Int32Array(new SharedArrayBuffer(4)))
and have the wait function call Atomics.wait(WAIT_BUFFER, 0, 0, ms) instead of
creating a new SharedArrayBuffer each invocation; update references to "buffer"
in the function to use WAIT_BUFFER so allocation churn is removed while keeping
Atomics.wait semantics.
🪄 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: Pro Plus

Run ID: ac39f487-def3-49eb-bf0d-9bdac481c3b6

📥 Commits

Reviewing files that changed from the base of the PR and between 946c52b and fc2276f.

📒 Files selected for processing (7)
  • src/lib/agent-onboard.ts
  • src/lib/deploy.ts
  • src/lib/nim.ts
  • src/lib/onboard.ts
  • src/lib/wait.ts
  • src/nemoclaw.ts
  • test/wait.test.ts

Comment thread src/lib/deploy.ts
Comment thread src/lib/onboard.ts
Comment thread src/lib/wait.ts
Comment thread test/wait.test.ts
@wscurran wscurran added NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). refactor This is a refactor of the code and/or architecture. labels Apr 20, 2026
@wscurran
Copy link
Copy Markdown
Contributor

✨ Thanks for submitting this PR that proposes a refactor to replace spawnSync('sleep') with a native wait utility, which could help improve the performance and robustness of the CLI.


Possibly related open issues:

@jyaunches
Copy link
Copy Markdown
Contributor

Hey @ksapru — nice work on this! I rebased on main and ran CI on a parallel PR (#2111) and hit two issues you'll want to fix before this can merge:

1. test/wait.test.ts:6.ts import extension fails tsc-cli check

- import { sleepMs, sleepSeconds } from "../src/lib/wait.ts";
+ import { sleepMs, sleepSeconds } from "../src/lib/wait.js";

The CLI tsconfig doesn't have allowImportingTsExtensions enabled. Other tests in test/ use .js extensions (e.g., duration.test.ts, gateway-state.test.ts).

2. src/lib/nim.ts:276 — stale ESLint suppression comment

The old line was require("child_process").spawnSync(...) which needed @typescript-eslint/no-require-imports suppression. Now it's just sleepSeconds(intervalSec) — the comment is orphaned:

-    // eslint-disable-next-line @typescript-eslint/no-require-imports
     sleepSeconds(intervalSec);

Once those are in, this should be green. I'll close #2111 in favor of yours once you've got it. 👍

@ksapru
Copy link
Copy Markdown
Contributor Author

ksapru commented Apr 20, 2026

Sounds good, thanks @jyaunches. I'll get this done shortly.

@jyaunches
Copy link
Copy Markdown
Contributor

Approved @brandonpelfrey can you merge?

@brandonpelfrey brandonpelfrey merged commit 666796e into NVIDIA:main Apr 21, 2026
15 checks passed
@cv cv added the v0.0.22 Release target label Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). refactor This is a refactor of the code and/or architecture. v0.0.22 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants