Skip to content

feat(cli): add self-update command#644

Closed
vasanth53 wants to merge 24 commits into
NVIDIA:mainfrom
vasanth53:feat/self-update-v2
Closed

feat(cli): add self-update command#644
vasanth53 wants to merge 24 commits into
NVIDIA:mainfrom
vasanth53:feat/self-update-v2

Conversation

@vasanth53

@vasanth53 vasanth53 commented Mar 22, 2026

Copy link
Copy Markdown

Summary

  • Add nemoclaw update command to check for CLI updates
  • Add nemoclaw update --yes to automatically update to latest version
  • Add nemoclaw update --force to force update check
  • Detects running from source and suggests git pull instead
  • Uses npm registry and GitHub releases for version check

Usage

nemoclaw update          # Check for updates
nemoclaw update --yes    # Update to latest (non-interactive)
nemoclaw update --force  # Force update even if current

Testing

  • Help text shows new update command
  • Version check works correctly
  • Source detection works (suggests git pull)

Related Issue

Closes #642

Summary by CodeRabbit

  • New Features
    • Added a CLI update command to check for and install the latest release.
    • Chooses the highest available version from GitHub and npm and reports current/latest status.
    • Supports --yes to skip prompts and --force to force installation.
    • Detects running-from-source and prevents auto-update, advising git instead.
    • Downloads and verifies the installer (SHA-256), runs it, reports outcome, and cleans up.

Add completion command supporting bash, zsh, and fish shells.
Completes issue NVIDIA#155.
- Add 'nemoclaw update' command to check for updates
- Add 'nemoclaw update --yes' to update without prompting
- Add 'nemoclaw update --force' to force update check
- Detects running from source and suggests 'git pull' instead
- Uses npm registry and GitHub releases for version check

Closes NVIDIA#642
@coderabbitai

coderabbitai Bot commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

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

Adds a new self-update feature and CLI command: bin/lib/update.js implements version checks (npm + GitHub), fetching with redirects/timeouts, semver comparison, CLI-path detection, and an installer runner; bin/nemoclaw.js registers nemoclaw update with --yes and --force to invoke the updater.

Changes

Cohort / File(s) Summary
Update Module
bin/lib/update.js
New module implementing HTTP(S) fetchUrl with redirects/timeouts, semver normalization/comparison, current-version and CLI-path detection, GitHub + npm latest-version lookups, checkForUpdate() and runUpdate() that download install.sh, compute SHA‑256 prefix, write/exec the script, handle errors, and cleanup.
CLI Integration
bin/nemoclaw.js
Added "update" to GLOBAL_COMMANDS, new update(opts) handler that lazy-loads ./lib/update and calls runUpdate(opts), dispatch case for update parsing --force/--yes, and updated help text documenting the command.

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI as "nemoclaw CLI"
    participant Update as "bin/lib/update.js"
    participant NPM as "npm Registry"
    participant GitHub as "GitHub Releases"
    participant Shell as "bash / shell"

    User->>CLI: nemoclaw update [--force] [--yes]
    CLI->>Update: runUpdate({force, yes})
    Update->>Update: getCurrentVersion() / getCurrentCliPath()
    Update->>NPM: getLatestNpmVersion()
    NPM-->>Update: npm version
    Update->>GitHub: getLatestVersion()
    GitHub-->>Update: release version
    Update->>Update: select higher latest, compare semver
    alt updateAvailable or force
        alt not yes
            Update->>User: prompt for confirmation
            User-->>Update: confirm/deny
        end
        alt confirmed
            Update->>Update: download install.sh (fetchUrl)
            Update->>Update: compute SHA-256 prefix, write exec script
            Update->>Shell: execute install.sh (bash) with inherited stdio
            Shell-->>Update: exit status
            Update->>Update: re-read local version, cleanup temp
            Update->>User: report success/failure
        end
    else no update
        Update->>User: report already up-to-date
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I sniffed the versions, near and far,
I fetched the tags from npm and the GitHub star,
I downloaded a script and checked its hash,
I hopped and ran it in a bashy dash,
Now the CLI leaps — updated in a flash!

🚥 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 'feat(cli): add self-update command' directly and clearly summarizes the main change: adding a self-update CLI command to NemoClaw.
Linked Issues check ✅ Passed All coding requirements from issue #642 are met: version checking against npm/GitHub, version display, non-interactive updates, --force and --yes flags, source detection with git pull suggestion, built-in http/https only, SHA256 verification, and edge case handling.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the self-update feature: bin/lib/update.js adds the update logic and bin/nemoclaw.js integrates the new 'update' CLI command with help text.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.

✏️ 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.

@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: 7

🧹 Nitpick comments (1)
bin/nemoclaw.js (1)

438-441: Inconsistent help text formatting.

Other section headers use the green color format ${G}Section:${R} (e.g., "Getting Started:", "Sandbox Management:"), but "Update:" is plain text.

✨ Proposed fix for consistent styling
-  Update:
-    nemoclaw update                    Check for updates
-    nemoclaw update --yes             Update to latest version
+  ${G}Update:${R}
+    nemoclaw update                  Check for updates
+    nemoclaw update --yes            Update to latest version
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/nemoclaw.js` around lines 438 - 441, Replace the plain "Update:" help
header with the colored format used elsewhere by changing the displayed header
string from "Update:" to `${G}Update:${R}` in the help output generator (the
block that prints the commands and headers where "Update:" currently appears);
ensure the rest of the two lines under that header remain unchanged and preserve
spacing/indentation to match other sections like "Getting Started:" and "Sandbox
Management:".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/update.js`:
- Around line 207-209: getCurrentVersion() can return a cached package.json
value after the installer runs; update the code so getCurrentVersion() returns
the fresh version (either by clearing Node's require cache for the package.json
module before calling require, e.g. delete
require.cache[require.resolve("../../package.json")], or by changing
getCurrentVersion() to read and JSON.parse fs.readFileSync of the package.json
path), then call that refreshed getCurrentVersion() for the success message;
ensure the change targets the getCurrentVersion() implementation (or the call
site right before console.log) so the logged v${newVersion} reflects the newly
written package.json.
- Around line 190-205: The downloaded installer is executed without integrity
verification and the cleanup uses fs.rmdirSync(tmpDir) which will fail if the
installer created files; update the flow around fetchUrl/INSTALL_SCRIPT_URL and
execSync to first obtain and verify the script's SHA256 (either by comparing
against an embedded expected hash or by fetching a .sha256 alongside the script)
and only proceed to write and execute the script if the checksum matches, and
change the cleanup to remove the temp dir recursively/forcibly (replace
fs.rmdirSync(tmpDir) with a recursive removal like fs.rmSync(tmpDir, {
recursive: true, force: true }) or equivalent) while ensuring errors on
verification abort before execSync is called; reference the tmpDir, scriptPath,
fetchUrl, INSTALL_SCRIPT_URL and execSync usages when making the changes.
- Around line 21-31: The versionGte function incorrectly treats prerelease tags
as numeric parts; update versionGte to either use a proper semver comparator
(e.g., import and call semver.gte with the original strings) or normalize inputs
by stripping prerelease/build suffixes before parsing (split each input on '-'
and '+' and take the first part, then split on '.' and parse ints) so that
prerelease labels like "1.0.0-beta.1" are not parsed as "0-beta"; ensure you
keep the function name versionGte and its return semantics when applying the
change.
- Around line 38-59: The fetchUrl function currently omits a User-Agent header
and follows redirects recursively without a depth limit; update fetchUrl to
include a sensible User-Agent header in the lib.get/request options (e.g.,
"User-Agent": "<your-app-name>") and add a redirect depth guard by accepting or
tracking a maxRedirects counter (e.g., maxRedirects param or internal attempts
variable) and reject if exceeded (suggest 5); replace the unbounded recursive
call fetchUrl(res.headers.location).then(...) with a call that passes/updates
the remaining redirect count and rejects with a clear error when the limit is
reached, keeping existing timeout and error handling intact.
- Around line 119-121: The source-detection in checkForUpdate is wrong: change
the runningFromSource calculation in checkForUpdate (where getCurrentCliPath()
is used) to only treat the CLI as "from source" when cliPath is falsy (i.e.,
runningFromSource = !cliPath) and remove the
cliPath.includes("node_modules/.bin") condition; also ensure any downstream
messages that instruct "use 'git pull' to update" are only shown when
runningFromSource is true so local project installs in node_modules are not
misclassified as source installs.

In `@bin/nemoclaw.js`:
- Line 484: The update command registers the --force flag in the invocation
(case "update": await update({ force: args.includes("--force"), yes:
args.includes("--yes") });) but the CLI help only documents --yes; update the
CLI help/usage text to include a short description for --force (e.g., "Bypass
update availability checks and force an update") alongside --yes so users see
both flags; locate where the update command help/usage string is defined and add
the --force entry and example usage referencing the update command and flags.
- Around line 44-48: Remove the unused constants SANDBOX_ACTIONS and SHELL_TYPES
from the file: locate the constant declarations for SANDBOX_ACTIONS and
SHELL_TYPES in bin/nemoclaw.js and delete them; if they are intended as
placeholders for future functionality, instead keep them but add a clear
explanatory comment above each declaring their intended purpose and why they
remain unused so linters/reviewers understand they’re intentional.

---

Nitpick comments:
In `@bin/nemoclaw.js`:
- Around line 438-441: Replace the plain "Update:" help header with the colored
format used elsewhere by changing the displayed header string from "Update:" to
`${G}Update:${R}` in the help output generator (the block that prints the
commands and headers where "Update:" currently appears); ensure the rest of the
two lines under that header remain unchanged and preserve spacing/indentation to
match other sections like "Getting Started:" and "Sandbox Management:".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b74decfe-9077-4bca-9eb9-648a1e474858

📥 Commits

Reviewing files that changed from the base of the PR and between 04012f7 and e549689.

📒 Files selected for processing (2)
  • bin/lib/update.js
  • bin/nemoclaw.js

Comment thread bin/lib/update.js Outdated
Comment thread bin/lib/update.js Outdated
Comment thread bin/lib/update.js Outdated
Comment thread bin/lib/update.js Outdated
Comment on lines +190 to +205
try {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-update-"));
const scriptPath = path.join(tmpDir, "install.sh");

console.log(" Downloading installer...");
const scriptContent = await fetchUrl(INSTALL_SCRIPT_URL);
fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });

console.log(" Running installer...");
execSync(`bash "${scriptPath}"`, {
stdio: "inherit",
cwd: tmpDir
});

fs.unlinkSync(scriptPath);
fs.rmdirSync(tmpDir);

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

Missing install script integrity verification.

The PR objectives state: "verify install script integrity before execution." Currently, the script is downloaded and executed without any checksum or signature verification. While HTTPS provides transport security, it doesn't protect against a compromised source.

Additionally, fs.rmdirSync(tmpDir) will fail if the installer creates additional files in the temp directory.

🔧 Proposed fix for cleanup (integrity check requires infrastructure)
-    fs.unlinkSync(scriptPath);
-    fs.rmdirSync(tmpDir);
+    // Clean up temp directory recursively
+    fs.rmSync(tmpDir, { recursive: true, force: true });

For integrity verification, consider:

  1. Embedding an expected SHA256 hash and verifying before execution
  2. Fetching a .sha256 file alongside the script and comparing
  3. Using GPG signature verification if available
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/update.js` around lines 190 - 205, The downloaded installer is
executed without integrity verification and the cleanup uses
fs.rmdirSync(tmpDir) which will fail if the installer created files; update the
flow around fetchUrl/INSTALL_SCRIPT_URL and execSync to first obtain and verify
the script's SHA256 (either by comparing against an embedded expected hash or by
fetching a .sha256 alongside the script) and only proceed to write and execute
the script if the checksum matches, and change the cleanup to remove the temp
dir recursively/forcibly (replace fs.rmdirSync(tmpDir) with a recursive removal
like fs.rmSync(tmpDir, { recursive: true, force: true }) or equivalent) while
ensuring errors on verification abort before execSync is called; reference the
tmpDir, scriptPath, fetchUrl, INSTALL_SCRIPT_URL and execSync usages when making
the changes.

Comment thread bin/lib/update.js Outdated
Comment thread bin/nemoclaw.js Outdated
Comment thread bin/nemoclaw.js Outdated
- versionGte: strip prerelease/build suffixes before parsing
- fetchUrl: add User-Agent header and 5-redirect limit
- checkForUpdate: fix source detection (only when cliPath is falsy)
- runUpdate: add SHA256 hash display, use fs.rmSync with recursive/force
- getCurrentVersion: add clearCache param to read fresh version
- bin/nemoclaw.js: remove unused SANDBOX_ACTIONS and SHELL_TYPES
- help text: fix Update section formatting, add --force flag docs
@vasanth53

vasanth53 commented Mar 22, 2026

Copy link
Copy Markdown
Author

Thanks for the review! All comments addressed:

  • versionGte: now strips prerelease/build suffixes before parsing
  • fetchUrl: added User-Agent header + 5-redirect limit
  • checkForUpdate: fixed source detection to only check !cliPath
  • runUpdate: added SHA256 hash display, uses fs.rmSync with recursive/force
  • getCurrentVersion: added clearCache param for fresh version read
  • Removed unused SANDBOX_ACTIONS and SHELL_TYPES constants
  • Fixed Update help section formatting + added --force flag docs

@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

♻️ Duplicate comments (1)
bin/lib/update.js (1)

209-217: ⚠️ Potential issue | 🟠 Major

Installer integrity is still not verified before execution.

Line 210 computes SHA-256, but the value is only logged before execSync runs the script. There is no trusted expected hash/signature comparison, so this does not provide integrity protection.

Proposed verification gate (example)
+    const EXPECTED_INSTALL_SHA256 = process.env.NEMOCLAW_INSTALL_SHA256; // or fetch trusted checksum/signature
     const hash = crypto.createHash("sha256").update(scriptContent).digest("hex");
-    console.log(`  Script SHA256: ${hash.substring(0, 16)}...`);
+    if (!EXPECTED_INSTALL_SHA256 || hash !== EXPECTED_INSTALL_SHA256) {
+      throw new Error("Installer integrity verification failed");
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/update.js` around lines 209 - 217, The code computes a SHA-256 for
scriptContent (variable hash) and immediately executes the scriptPath via
execSync without verifying it; add a verification gate that compares the
computed hash against a trusted expected value (provided either as a constant,
an environment variable, or a downloaded/packaged signature) and aborts with an
error/log if they differ before calling execSync; update the logic around hash,
scriptPath, scriptContent, and the execSync call to perform this comparison and
only run the installer when the hashes match.
🧹 Nitpick comments (1)
bin/lib/update.js (1)

136-141: Unreachable condition in source/version detection.

runningFromSource is !cliPath (Line 133), so runningFromSource && cliPath can never be true. This branch is dead and should be removed or corrected.

Proposed cleanup
-  if (runningFromSource && cliPath) {
+  if (!runningFromSource && cliPath) {
     try {
       const output = execSync(`"${cliPath}" --version 2>/dev/null`, { encoding: "utf-8" });
       const match = output.match(/(\d+\.\d+\.\d+)/);
       if (match) current = match[1];
     } catch {}
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/update.js` around lines 136 - 141, The branch checking "if
(runningFromSource && cliPath)" is unreachable because runningFromSource is set
to "!cliPath"; replace the condition so the CLI version probe runs when a
cliPath exists (e.g., use "if (cliPath)" or "if (!runningFromSource &&
cliPath)") or remove the dead block entirely; update the block around
execSync/version matching (referencing runningFromSource and cliPath)
accordingly to ensure the external CLI is queried only when cliPath is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/update.js`:
- Around line 178-188: When no newer version is found but the user passed a
force flag, the current block still prints "A new version is available!" and
suggests using '--yes'; change the logic around the variables yes,
runningFromSource and the force flag (e.g., force) so that if force is true you
do not claim a new version exists but instead print a message like "No new
version available; --force will reinstall the current version" and, when not
runningFromSource, suggest using 'nemoclaw update --force' (and when
runningFromSource, keep the git pull hint). Update the messages and suggested
command accordingly in the code paths that reference yes and runningFromSource
so forced behavior is correctly described.

In `@bin/nemoclaw.js`:
- Around line 39-40: GLOBAL_COMMANDS includes the string "completion" but the
global dispatch switch that handles commands lacks a corresponding case so it
falls through to help(); either remove "completion" from GLOBAL_COMMANDS if it
shouldn't be a global command, or add a proper case "completion" branch in the
global dispatch (the same switch that currently calls help()) that invokes the
intended completion handler (e.g., call the existing completion implementation
or a new completionHandler function). Update the switch to include case
"completion": ... and ensure the handler is imported/defined and
returns/executes consistently with other global commands.

---

Duplicate comments:
In `@bin/lib/update.js`:
- Around line 209-217: The code computes a SHA-256 for scriptContent (variable
hash) and immediately executes the scriptPath via execSync without verifying it;
add a verification gate that compares the computed hash against a trusted
expected value (provided either as a constant, an environment variable, or a
downloaded/packaged signature) and aborts with an error/log if they differ
before calling execSync; update the logic around hash, scriptPath,
scriptContent, and the execSync call to perform this comparison and only run the
installer when the hashes match.

---

Nitpick comments:
In `@bin/lib/update.js`:
- Around line 136-141: The branch checking "if (runningFromSource && cliPath)"
is unreachable because runningFromSource is set to "!cliPath"; replace the
condition so the CLI version probe runs when a cliPath exists (e.g., use "if
(cliPath)" or "if (!runningFromSource && cliPath)") or remove the dead block
entirely; update the block around execSync/version matching (referencing
runningFromSource and cliPath) accordingly to ensure the external CLI is queried
only when cliPath is present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 517b0cbb-7c4f-4433-bf0f-a76ec9f897c7

📥 Commits

Reviewing files that changed from the base of the PR and between e549689 and dfddeda.

📒 Files selected for processing (2)
  • bin/lib/update.js
  • bin/nemoclaw.js

Comment thread bin/lib/update.js Outdated
Comment thread bin/nemoclaw.js Outdated
- Fix --force message when no new version (show 'reinstalling current version')
- Remove unused 'completion' from GLOBAL_COMMANDS (no handler exists)
@vasanth53

Copy link
Copy Markdown
Author

Addressed remaining comments:

  • Fixed --force message when no new version available (now shows 'Reinstalling current version')
  • Removed 'completion' from GLOBAL_COMMANDS (no handler exists)

@wscurran

Copy link
Copy Markdown
Contributor

Thanks for the self-update command. The CLI has been refactored to TypeScript since March. Could you rebase both this and #472 against main and update the implementations to fit the current architecture? Happy to review once both are updated.

@vasanth53 vasanth53 force-pushed the feat/self-update-v2 branch from 00a7c59 to f50b6f1 Compare April 19, 2026 13:11
@vasanth53

Copy link
Copy Markdown
Author

@wscurran I’ve rebased with the latest changes against main. Could you please review once?

@copy-pr-bot

copy-pr-bot Bot commented Apr 23, 2026

Copy link
Copy Markdown

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.

@cv cv added v0.0.30 and removed v0.0.30 labels Apr 29, 2026
@cv cv added v0.0.32 and removed v0.0.31 labels Apr 30, 2026
@ericksoa

Copy link
Copy Markdown
Contributor

Thanks for the contribution here. I am going to close this PR as superseded by the current installer-driven update flow.

Users can achieve the same outcome with the supported commands on main:

  • curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash
  • nemoclaw upgrade-sandboxes --check
  • nemoclaw upgrade-sandboxes

For extra safety before an upgrade, users can also create a snapshot first:

  • nemoclaw <sandbox-name> snapshot create --name pre-upgrade

The installer is the maintained path for updating the CLI and it also integrates with the sandbox-upgrade flow, so keeping a separate self-update implementation open would be confusing at this point. Thank you again for proposing and working through this feature.

@ericksoa ericksoa closed this Apr 30, 2026
@wscurran wscurran added area: cli Command line interface, flags, terminal UX, or output feature PR adds or expands user-visible functionality needs: review PR is conflict-free and awaiting maintainer review and removed NemoClaw CLI needs: review PR is conflict-free and awaiting maintainer review labels Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli Command line interface, flags, terminal UX, or output feature PR adds or expands user-visible functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cli): add self-update command for automatic CLI updates

4 participants