Skip to content

feat(cli): add nemoclaw <sandbox> skill remove command#1962

Closed
sinha-abhash wants to merge 13 commits into
NVIDIA:mainfrom
sinha-abhash:feat/skill-remove-command
Closed

feat(cli): add nemoclaw <sandbox> skill remove command#1962
sinha-abhash wants to merge 13 commits into
NVIDIA:mainfrom
sinha-abhash:feat/skill-remove-command

Conversation

@sinha-abhash

@sinha-abhash sinha-abhash commented Apr 16, 2026

Copy link
Copy Markdown

Summary

Adds nemoclaw <sandbox> skill remove <name> to complement the existing skill install command (#1844 / #1845), providing a complete skill lifecycle from the CLI.

Motivation

After skill install was added, there was no way to cleanly remove a skill from a running sandbox. Users had to manually SSH in, delete two directories, and clear sessions.json — mirroring the same problem skill install was created to solve.

What this does

nemoclaw dtx-claw skill remove my-skill
  ✓ Skill 'my-skill' removed
  • Removes the immutable upload dir: /sandbox/.openclaw/skills/<name>/
  • Removes the OpenClaw mirror dir: $HOME/.openclaw/skills/<name>/
  • Clears sessions.json so the agent re-discovers remaining skills on the next session
  • Verifies the directory is gone after removal
  • Validates the skill name against [A-Za-z0-9._-] before any SSH operation — shell injection rejected client-side
  • Prints a clear error when the named skill is not installed

Usage

nemoclaw <sandbox> skill install <path>   # existing
nemoclaw <sandbox> skill remove <name>   # new

Run nemoclaw <sandbox> skill --help for full usage.

Edge cases tested

Scenario Result
Multi-file skill with subdirectory Entire directory tree removed cleanly
Wrong case name not installed error, exit 1
Missing name argument Usage printed, exit 1
Shell injection (skill;rm -rf /, ../../../etc/passwd, $(evil)) Rejected by validateSkillName before any SSH call
Remove skill while TUI is active Skill gone on next reconnect (same behaviour as install)
sessions.json cleared Confirmed {} after removal

Changes

src/lib/skill-install.ts

  • Added removeSkill() — removes upload dir, mirror dir, clears sessions
  • Added verifyRemove() — confirms directory is gone post-removal
  • Added validateSkillName() — rejects shell metacharacters in CLI name input

src/nemoclaw.ts

  • Extended sandboxSkillInstall() to dispatch install vs remove
  • Updated --help output and global help text
  • SSH config temp file always cleaned up in finally block

src/lib/skill-install.test.ts

  • Added validateSkillName tests — valid names, empty, spaces, metacharacters
  • Added removeSkill unit test — SSH-failure path returns success=false + warning
  • Added verifyRemove unit test — SSH-failure returns false (conservative/safe)

All 1611 existing tests continue to pass.

Summary by CodeRabbit

  • New Features

    • Added a CLI command to remove installed skills, with name validation that rejects "." and ".." and enforces allowed characters.
    • Removal now deletes the upload directory and, when applicable, the agent home-directory mirror and refreshes session indexing (or advises gateway restart).
  • Documentation

    • Added command reference and usage example for skill removal, validation rules, and agent-specific effects.
  • Tests

    • New unit tests covering name validation, removal behavior, and SSH-unreachable conservative outcomes.

Review Change Stack

@coderabbitai

coderabbitai Bot commented Apr 16, 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: 1de15787-ff24-4f2b-9dd4-d7d818c145c4

📥 Commits

Reviewing files that changed from the base of the PR and between 36d37c2 and 4b74e92.

📒 Files selected for processing (2)
  • docs/reference/commands.mdx
  • src/lib/cli/public-display-defaults.ts
💤 Files with no reviewable changes (1)
  • src/lib/cli/public-display-defaults.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/reference/commands.mdx

📝 Walkthrough

Walkthrough

Adds CLI documentation and public-display metadata for nemoclaw <name> skill remove <name>, and updates .gitleaksignore to suppress two discord-client-secret findings in the nightly E2E workflow.

Changes

Skill Removal Feature

Layer / File(s) Summary
Docs: skill remove command and examples
docs/reference/commands.mdx
Adds documentation for nemoclaw <name> skill remove <name>, including name validation rules and removal effects.
Public CLI display entry
src/lib/cli/public-display-defaults.ts
Introduces a PUBLIC_DISPLAY_LAYOUT entry for sandbox:skill:remove so the command appears in public CLI listings.
CI secret ignore
.gitleaksignore
Adds two .gitleaksignore patterns to suppress discord-client-secret leak findings in the nightly-e2e workflow file.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

documentation

Suggested reviewers

  • cv

Poem

🐰 I hopped through docs to make things neat,
A remove command tidy and sweet.
Names now checked, no dots allowed,
CLI listings sing, the CI's unbowed.
Hop, skip, a patch — now ship with a tweet!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 accurately describes the main feature being added—a new CLI command for removing skills from sandboxes—and is concise and specific.
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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

🧹 Nitpick comments (1)
src/nemoclaw.ts (1)

1340-1568: Split skill install and skill remove into dedicated handlers.

sandboxSkillInstall() now owns subcommand help, argument validation, two SSH-config lifecycles, and both install/remove workflows. That pushes this dispatcher well past the repo's complexity target and makes the next skill subcommand harder to change safely. Extract the remove flow and install flow into separate helpers, and leave this function to parse the subcommand and delegate.

As per coding guidelines, Limit cyclomatic complexity to 20 in JavaScript/TypeScript files, with target of 15.

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

In `@src/nemoclaw.ts` around lines 1340 - 1568, Current sandboxSkillInstall is
doing parsing plus both install and remove workflows; extract the remove and
install workflows into two small helpers (e.g.
handleSkillRemove(ctxNameOrSandbox, args) and
handleSkillInstall(ctxNameOrSandbox, args) or similar) and leave
sandboxSkillInstall to only parse subcommand, validate arguments, and delegate.
Move the SSH-config lifecycle (captureOpenshell(...), tmpSshConfig creation,
fs.writeFileSync, and fs.unlinkSync cleanup) into the helpers so each has its
own try/finally and uses the same ctx shape ({ configFile, sandboxName }); move
code that validates skillName (skillInstall.validateSkillName),
checkExisting/remove/verifyRemove into the remove helper (use
agentRuntime.getSessionAgent and skillInstall.resolveSkillPaths there) and move
frontmatter parsing, collectFiles, uploadDirectory, postInstall, and
verifyInstall into the install helper (again resolving agent and paths inside
it). Ensure sandboxSkillInstall still performs top-level argument/usage printing
and only calls the appropriate helper with sandboxName and the remaining args.
🤖 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/skill-install.ts`:
- Around line 423-428: validateSkillName currently allows "." and ".." because
the regex matches them; update validateSkillName(name: string) to explicitly
reject a single "." and a double ".." (e.g., check name !== "." && name !==
"..") while keeping the existing /^[A-Za-z0-9._-]+$/ and length > 0 checks so
behavior remains the same for all other names; also ensure this change is
mirrored where the earlier frontmatter/skill-name validation is implemented so
both checks stay aligned.
- Around line 379-420: The removal currently reports success and verifyRemove()
checks only paths.uploadDir so if paths.mirrorDir (OpenClaw mirror) wasn't
removed the CLI can lie; update the removal return value and verification to
require the mirror deletion when paths.isOpenClaw is true: in the remover
function make success combine removedUploadDir && (if paths.isOpenClaw ?
removedMirrorDir : true) and in verifyRemove(ctx, paths) have sshExec test both
shellQuote(paths.uploadDir) and, when paths.isOpenClaw && paths.mirrorDir,
shellQuote(paths.mirrorDir) (both must be GONE); also update the existence check
logic used by checkExisting() to probe the mirror SKILL.md
(paths.mirrorDir/SKILL.md) in addition to uploadDir/SKILL.md so retries won't be
blocked.

---

Nitpick comments:
In `@src/nemoclaw.ts`:
- Around line 1340-1568: Current sandboxSkillInstall is doing parsing plus both
install and remove workflows; extract the remove and install workflows into two
small helpers (e.g. handleSkillRemove(ctxNameOrSandbox, args) and
handleSkillInstall(ctxNameOrSandbox, args) or similar) and leave
sandboxSkillInstall to only parse subcommand, validate arguments, and delegate.
Move the SSH-config lifecycle (captureOpenshell(...), tmpSshConfig creation,
fs.writeFileSync, and fs.unlinkSync cleanup) into the helpers so each has its
own try/finally and uses the same ctx shape ({ configFile, sandboxName }); move
code that validates skillName (skillInstall.validateSkillName),
checkExisting/remove/verifyRemove into the remove helper (use
agentRuntime.getSessionAgent and skillInstall.resolveSkillPaths there) and move
frontmatter parsing, collectFiles, uploadDirectory, postInstall, and
verifyInstall into the install helper (again resolving agent and paths inside
it). Ensure sandboxSkillInstall still performs top-level argument/usage printing
and only calls the appropriate helper with sandboxName and the remaining args.
🪄 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: 3f679918-19f5-4eff-b916-7665b1b7d101

📥 Commits

Reviewing files that changed from the base of the PR and between d90096d and 68beb05.

📒 Files selected for processing (3)
  • src/lib/skill-install.test.ts
  • src/lib/skill-install.ts
  • src/nemoclaw.ts

Comment thread src/lib/skill-install.ts
Comment thread src/lib/skill-install.ts Outdated
@wscurran

Copy link
Copy Markdown
Contributor

✨ Thanks for submitting this PR that proposes an enhancement to add a skill remove command, which could help improve the user experience and provide a complete skill lifecycle from the CLI.


Possibly related open PRs:


Possibly related open issues:

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

♻️ Duplicate comments (1)
src/lib/skill-install.ts (1)

405-420: ⚠️ Potential issue | 🟠 Major

Require mirror deletion for OpenClaw before reporting removal success.

On Line [406], success ignores mirror deletion; and Lines [417-420] verify only uploadDir. In OpenClaw mode, this can report “removed” while $HOME/.openclaw/skills/<name> still exists.

Proposed fix
 export function removeSkill(
   ctx: SshContext,
   paths: SkillPaths,
 ): RemoveResult {
@@
   return {
-    success: removedUploadDir,
+    success: removedUploadDir && (!paths.isOpenClaw || removedMirrorDir),
     removedUploadDir,
     removedMirrorDir,
     clearedSessions,
     messages,
   };
 }
@@
 export function verifyRemove(ctx: SshContext, paths: SkillPaths): boolean {
-  const target = shellQuote(paths.uploadDir);
-  const result = sshExec(ctx, `test -e ${target} && echo EXISTS || echo GONE`);
+  const checks = [`test ! -e ${shellQuote(paths.uploadDir)}`];
+  if (paths.isOpenClaw && paths.mirrorDir) {
+    checks.push(`test ! -e "${paths.mirrorDir}"`);
+  }
+  const result = sshExec(ctx, `${checks.join(" && ")} && echo GONE || echo EXISTS`);
   return result !== null && result.stdout === "GONE";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/skill-install.ts` around lines 405 - 420, The removal success
currently ignores mirror deletion; update the code so success requires the
mirror to be removed when applicable and make verifyRemove check both upload and
mirror dirs: modify the caller that builds the result object to set success =
removedUploadDir && (paths.mirrorDir ? removedMirrorDir : true) (use the
existing removedUploadDir and removedMirrorDir flags), and change the
verifyRemove(ctx: SshContext, paths: SkillPaths) function to shell-quote and
test both paths.uploadDir and paths.mirrorDir (if paths.mirrorDir is provided) —
return true only when both are gone (i.e., test each with sshExec and ensure
both echo GONE).
🧹 Nitpick comments (1)
src/lib/skill-install.ts (1)

66-70: Consolidate skill-name validation into one shared helper.

The same rule is duplicated in parseFrontmatter and validateSkillName. Centralizing it reduces drift risk and keeps install/remove behavior consistent.

Refactor sketch
+const SKILL_NAME_RE = /^[A-Za-z0-9._-]+$/;
+function isValidSkillName(name: string): boolean {
+  return name.length > 0 && name !== "." && name !== ".." && SKILL_NAME_RE.test(name);
+}
@@
-  if (nameValue === "." || nameValue === ".." || !/^[A-Za-z0-9._-]+$/.test(nameValue)) {
+  if (!isValidSkillName(nameValue)) {
@@
 export function validateSkillName(name: string): boolean {
-  return (
-    name.length > 0 &&
-    name !== "." &&
-    name !== ".." &&
-    /^[A-Za-z0-9._-]+$/.test(name)
-  );
+  return isValidSkillName(name);
 }

Also applies to: 427-433

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

In `@src/lib/skill-install.ts` around lines 66 - 70, Extract the skill-name
validation logic into a single helper (e.g., isValidSkillName or
validateSkillNameFormat) and use it from both parseFrontmatter and
validateSkillName instead of duplicating the regex and "."/".." checks;
implement the helper to return boolean or throw with the same error text
(`SKILL.md name '${name}' contains invalid characters. Only [A-Za-z0-9._-]
allowed.`) and replace the inline checks in parseFrontmatter and the existing
validateSkillName to call the new helper so install/remove behavior is
consistent.
🤖 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/skill-install.ts`:
- Around line 405-420: The removal success currently ignores mirror deletion;
update the code so success requires the mirror to be removed when applicable and
make verifyRemove check both upload and mirror dirs: modify the caller that
builds the result object to set success = removedUploadDir && (paths.mirrorDir ?
removedMirrorDir : true) (use the existing removedUploadDir and removedMirrorDir
flags), and change the verifyRemove(ctx: SshContext, paths: SkillPaths) function
to shell-quote and test both paths.uploadDir and paths.mirrorDir (if
paths.mirrorDir is provided) — return true only when both are gone (i.e., test
each with sshExec and ensure both echo GONE).

---

Nitpick comments:
In `@src/lib/skill-install.ts`:
- Around line 66-70: Extract the skill-name validation logic into a single
helper (e.g., isValidSkillName or validateSkillNameFormat) and use it from both
parseFrontmatter and validateSkillName instead of duplicating the regex and
"."/".." checks; implement the helper to return boolean or throw with the same
error text (`SKILL.md name '${name}' contains invalid characters. Only
[A-Za-z0-9._-] allowed.`) and replace the inline checks in parseFrontmatter and
the existing validateSkillName to call the new helper so install/remove behavior
is consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a79c72d-a216-4456-a527-90d7ce0e709b

📥 Commits

Reviewing files that changed from the base of the PR and between 68beb05 and 36b87b1.

📒 Files selected for processing (2)
  • src/lib/skill-install.test.ts
  • src/lib/skill-install.ts

sinha-abhash pushed a commit to sinha-abhash/NemoClaw that referenced this pull request Apr 17, 2026
… OpenClaw mirror

- removeSkill(): success now requires both uploadDir and mirrorDir to be
  removed for OpenClaw sandboxes (previously only checked uploadDir, so
  a surviving mirror would still show the skill as removed)
- verifyRemove(): checks both uploadDir and mirrorDir via a compound SSH
  test before returning GONE (previously only checked uploadDir)
- checkExisting(): probes both uploadDir/SKILL.md and mirrorDir/SKILL.md
  so a partial removal (uploadDir gone, mirrorDir surviving) no longer
  blocks a reinstall

All 37 skill-install tests pass. Fixes address the Major issue flagged
by CodeRabbit on PR NVIDIA#1962.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

🧹 Nitpick comments (1)
src/lib/skill-install.ts (1)

66-70: Consolidate skill-name validation into a single helper.

Line [66] and Line [436] duplicate the same rules. Keeping one source of truth avoids future drift between install-time and CLI-time validation.

♻️ Proposed refactor
+function isValidSkillName(name: string): boolean {
+  return (
+    name.length > 0 &&
+    name !== "." &&
+    name !== ".." &&
+    /^[A-Za-z0-9._-]+$/.test(name)
+  );
+}
+
 export function parseFrontmatter(content: string): SkillFrontmatter {
   ...
-  if (nameValue === "." || nameValue === ".." || !/^[A-Za-z0-9._-]+$/.test(nameValue)) {
+  if (!isValidSkillName(nameValue)) {
     throw new Error(
       `SKILL.md name '${nameValue}' contains invalid characters. Only [A-Za-z0-9._-] allowed.`,
     );
   }
   ...
 }
 ...
 export function validateSkillName(name: string): boolean {
-  return (
-    name.length > 0 &&
-    name !== "." &&
-    name !== ".." &&
-    /^[A-Za-z0-9._-]+$/.test(name)
-  );
+  return isValidSkillName(name);
 }

Also applies to: 436-442

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

In `@src/lib/skill-install.ts` around lines 66 - 70, Extract the duplicated
validation into a single helper called e.g. validateSkillName(name: string) (or
isValidSkillName returning boolean) that encapsulates the checks against "." and
".." and the /^[A-Za-z0-9._-]+$/ regex and the error message (`SKILL.md name
'${name}' contains invalid characters. Only [A-Za-z0-9._-] allowed.`); replace
the inline check in the install flow that uses nameValue and the duplicate check
at the CLI side (the block around the other duplicate) to call this helper
instead (either throw from validateSkillName or check boolean and throw at the
call site) so both install-time and CLI-time use the same validation logic and
message.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/lib/skill-install.ts`:
- Around line 66-70: Extract the duplicated validation into a single helper
called e.g. validateSkillName(name: string) (or isValidSkillName returning
boolean) that encapsulates the checks against "." and ".." and the
/^[A-Za-z0-9._-]+$/ regex and the error message (`SKILL.md name '${name}'
contains invalid characters. Only [A-Za-z0-9._-] allowed.`); replace the inline
check in the install flow that uses nameValue and the duplicate check at the CLI
side (the block around the other duplicate) to call this helper instead (either
throw from validateSkillName or check boolean and throw at the call site) so
both install-time and CLI-time use the same validation logic and message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 68e25d3e-e4b7-4326-9fe4-4d96807d3b6e

📥 Commits

Reviewing files that changed from the base of the PR and between 36b87b1 and b5a8c53.

📒 Files selected for processing (2)
  • src/lib/skill-install.test.ts
  • src/lib/skill-install.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/skill-install.test.ts

@sinha-abhash

Copy link
Copy Markdown
Author

✨ Thanks for submitting this PR that proposes an enhancement to add a skill remove command, which could help improve the user experience and provide a complete skill lifecycle from the CLI.

Possibly related open PRs:

Possibly related open issues:

Hi @wscurran, thanks for the review!

#1845 and #1844 are related but not duplicates — #1845 (merged) implements skill install, and #1844 is the issue it closed. Skill removal was explicitly listed as out of scope in #1844 (see the "Out of scope" section: "Skill removal or update commands (follow-up)").

This PR is that follow-up, completing the skill lifecycle:

nemoclaw <sandbox> skill install <path>   # added in #1845
nemoclaw <sandbox> skill remove <name>    # this PR

Happy to add a reference to #1845 in the PR description to make the relationship clearer if that helps.

@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 `@src/lib/skill-install.ts`:
- Around line 81-84: The error thrown when validateSkillName(nameValue) fails
should explicitly mention reserved-name restrictions (e.g., "." and "..") as
distinct from invalid character rules; update the message in the thrown Error
inside the SKILL.md name check to state both: allowed characters [A-Za-z0-9._-]
and that names cannot be "." or ".." (or other reserved names if applicable),
referencing validateSkillName and nameValue so maintainers can locate and adjust
the error string accordingly.
🪄 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: 7afe3706-6ea6-456a-a029-99ba7cd28a93

📥 Commits

Reviewing files that changed from the base of the PR and between b5a8c53 and f418178.

📒 Files selected for processing (1)
  • src/lib/skill-install.ts

Comment thread src/lib/skill-install.ts

@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

🤖 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/nemoclaw.ts`:
- Around line 1380-1382: skillInstall.checkExisting currently conflates “absent”
and “probe failed” (see src/lib/skill-install.ts:349-356); change checkExisting
to return a tri-state/result object (for example {exists: boolean|null, error?:
Error}) or throw on probe failure, and then update the caller here to
distinguish outcomes: if exists === false report the skill is not installed, if
exists === null or an error was returned/logged then surface the probe error
(console.error with the error) and exit (or propagate) instead of printing “not
installed”. Reference: skillInstall.checkExisting, variables ctx, paths,
skillName, sandboxName.
- Around line 1394-1399: The current message assumes verifyRemove succeeded in
proving deletion, but skillInstall.verifyRemove(ctx, paths) can return false on
SSH/verification failure; update the error path where gone is false (the block
using gone, skillName and paths.uploadDir) to log that removal is unverified
rather than asserting the dir still exists—e.g. change the console.error message
to indicate "could not verify removal" and include skillName and paths.uploadDir
(or include the underlying error if available from verifyRemove) so callers know
verification failed rather than deletion was proven to have failed.
🪄 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: 798ae3cd-318f-4d60-800e-b93fbfb611b6

📥 Commits

Reviewing files that changed from the base of the PR and between f418178 and 8fa4c63.

📒 Files selected for processing (1)
  • src/nemoclaw.ts

Comment thread src/nemoclaw.ts Outdated
Comment thread src/nemoclaw.ts Outdated
@sinha-abhash

Copy link
Copy Markdown
Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Reviews resumed.

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

♻️ Duplicate comments (2)
src/lib/skill-install.ts (1)

354-363: ⚠️ Potential issue | 🟠 Major

Preserve the tri-state on non-zero SSH exits.

sshExec() only returns null when spawnSync() throws. A normal SSH transport/auth failure still comes back with a non-zero status, so this path still collapses probe failures into false instead of null. That means install can still treat an unreachable sandbox as a fresh install, and remove can still say the skill is not installed when the probe never completed.

🩹 Proposed fix
 export function checkExisting(ctx: SshContext, paths: SkillPaths): boolean | null {
   const checks = [`test -f ${shellQuote(`${paths.uploadDir}/SKILL.md`)}`];
   if (paths.isOpenClaw && paths.mirrorDir) {
     checks.push(`test -f "${paths.mirrorDir}/SKILL.md"`);
   }
   const result = sshExec(ctx, `{ ${checks.join(" || ")}; } && echo EXISTS || echo ABSENT`);
-  if (result === null) {
+  if (result === null || result.status !== 0) {
     return null;
   }
   return result.stdout === "EXISTS";
 }

Verify by comparing sshExec() and checkExisting(): if SSH failures can produce a non-zero exit without throwing, this function should map that case to null.

#!/bin/bash
sed -n '168,200p' src/lib/skill-install.ts
printf '\n---\n'
sed -n '344,364p' src/lib/skill-install.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/skill-install.ts` around lines 354 - 363, The checkExisting function
currently treats any non-null sshExec() result as a definitive false/true, but
sshExec can return a non-zero exit/status for SSH transport or auth failures
(not just throw), so update checkExisting (referencing checkExisting, sshExec,
SshContext, SkillPaths) to detect sshExec results where result.status !== 0 (or
an error indicated in result.stderr) and return null in that case; otherwise
keep the existing stdout === "EXISTS" logic. Ensure you surface the tri-state by
returning null for SSH failures, true when stdout is "EXISTS", and false
otherwise.
src/nemoclaw.ts (1)

1353-1356: ⚠️ Potential issue | 🟡 Minor

Mention reserved names in the CLI validation error.

validateSkillName() also rejects . and .., so this message is still misleading for those inputs. Surface the reserved-name rule here too so the CLI explains the actual failure.

✏️ Suggested tweak
   if (!skillInstall.validateSkillName(skillName)) {
     console.error(`  Invalid skill name: '${skillName}'`);
-    console.error("  Skill names must match [A-Za-z0-9._-].");
+    console.error("  Skill names must use [A-Za-z0-9._-] and cannot be '.' or '..'.");
     process.exit(1);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nemoclaw.ts` around lines 1353 - 1356, The CLI error for invalid skill
names is misleading because validateSkillName(skillName) also rejects the
reserved names "." and ".."; update the messages in the block that checks
validateSkillName(skillName) so the output mentions both the allowed pattern and
the reserved-name rule (e.g., state that "." and ".." are reserved and not
allowed), keeping the existing descriptive first line (`Invalid skill name:
'${skillName}'`) and replacing the second line to include the regex constraint
and the reserved names; locate this logic around the
skillInstall.validateSkillName call and adjust the console.error strings
accordingly.
🤖 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/skill-install.ts`:
- Around line 354-363: The checkExisting function currently treats any non-null
sshExec() result as a definitive false/true, but sshExec can return a non-zero
exit/status for SSH transport or auth failures (not just throw), so update
checkExisting (referencing checkExisting, sshExec, SshContext, SkillPaths) to
detect sshExec results where result.status !== 0 (or an error indicated in
result.stderr) and return null in that case; otherwise keep the existing stdout
=== "EXISTS" logic. Ensure you surface the tri-state by returning null for SSH
failures, true when stdout is "EXISTS", and false otherwise.

In `@src/nemoclaw.ts`:
- Around line 1353-1356: The CLI error for invalid skill names is misleading
because validateSkillName(skillName) also rejects the reserved names "." and
".."; update the messages in the block that checks validateSkillName(skillName)
so the output mentions both the allowed pattern and the reserved-name rule
(e.g., state that "." and ".." are reserved and not allowed), keeping the
existing descriptive first line (`Invalid skill name: '${skillName}'`) and
replacing the second line to include the regex constraint and the reserved
names; locate this logic around the skillInstall.validateSkillName call and
adjust the console.error strings accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1df3f5b4-0338-409f-b1c7-2d29d57409ef

📥 Commits

Reviewing files that changed from the base of the PR and between f418178 and f51e324.

📒 Files selected for processing (2)
  • src/lib/skill-install.ts
  • src/nemoclaw.ts

@sinha-abhash sinha-abhash force-pushed the feat/skill-remove-command branch from f51e324 to e1a7db7 Compare April 28, 2026 12:37
@copy-pr-bot

copy-pr-bot Bot commented Apr 28, 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.

sinha-abhash pushed a commit to sinha-abhash/NemoClaw that referenced this pull request Apr 28, 2026
… OpenClaw mirror

- removeSkill(): success now requires both uploadDir and mirrorDir to be
  removed for OpenClaw sandboxes (previously only checked uploadDir, so
  a surviving mirror would still show the skill as removed)
- verifyRemove(): checks both uploadDir and mirrorDir via a compound SSH
  test before returning GONE (previously only checked uploadDir)
- checkExisting(): probes both uploadDir/SKILL.md and mirrorDir/SKILL.md
  so a partial removal (uploadDir gone, mirrorDir surviving) no longer
  blocks a reinstall

All 37 skill-install tests pass. Fixes address the Major issue flagged
by CodeRabbit on PR NVIDIA#1962.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@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 `@src/lib/skill-install.ts`:
- Around line 365-375: checkExisting currently only returns null if sshExec
returns null, but sshExec can also return a non-null object with status !== 0 on
probe failure; update checkExisting to treat any non-successful sshExec result
(result === null OR result.status !== 0) as a probe failure and return null.
Locate the checkExisting function and change the result handling for the sshExec
call (the variable named result from sshExec) so that you return null when
result is null or result.status !== 0, and keep the existing EXISTS/ABSENT
stdout check (result.stdout === "EXISTS") for successful probes; ensure you
still build the same checks array using paths.uploadDir and paths.mirrorDir.
🪄 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: b7f6d165-6392-4fc7-b38c-24713addfb0f

📥 Commits

Reviewing files that changed from the base of the PR and between f51e324 and e1a7db7.

📒 Files selected for processing (12)
  • nemoclaw-blueprint/policies/presets/telegram.yaml
  • nemoclaw/src/blueprint/runner.ts
  • nemoclaw/src/lib/subprocess-env.ts
  • package.json
  • src/lib/command-registry.ts
  • src/lib/http-probe.test.ts
  • src/lib/skill-install.test.ts
  • src/lib/skill-install.ts
  • src/lib/validation.ts
  • src/nemoclaw.ts
  • test/check-docs-links.test.ts
  • test/credential-exposure.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/skill-install.test.ts
  • src/nemoclaw.ts

Comment thread src/lib/skill-install.ts
@sinha-abhash sinha-abhash force-pushed the feat/skill-remove-command branch from e1a7db7 to 8ea0d86 Compare April 29, 2026 06:12
sinha-abhash pushed a commit to sinha-abhash/NemoClaw that referenced this pull request Apr 29, 2026
… OpenClaw mirror

- removeSkill(): success now requires both uploadDir and mirrorDir to be
  removed for OpenClaw sandboxes (previously only checked uploadDir, so
  a surviving mirror would still show the skill as removed)
- verifyRemove(): checks both uploadDir and mirrorDir via a compound SSH
  test before returning GONE (previously only checked uploadDir)
- checkExisting(): probes both uploadDir/SKILL.md and mirrorDir/SKILL.md
  so a partial removal (uploadDir gone, mirrorDir surviving) no longer
  blocks a reinstall

All 37 skill-install tests pass. Fixes address the Major issue flagged
by CodeRabbit on PR NVIDIA#1962.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

♻️ Duplicate comments (1)
src/nemoclaw.ts (1)

2496-2499: ⚠️ Potential issue | 🟡 Minor

Invalid-name message is incomplete for reserved names.

Line 2498 says only regex constraints, but validation also rejects "." and "..”. Please align the CLI error to avoid misleading users.

✏️ Suggested tweak
   if (!skillInstall.validateSkillName(skillName)) {
     console.error(`  Invalid skill name: '${skillName}'`);
-    console.error("  Skill names must match [A-Za-z0-9._-].");
+    console.error("  Skill names must match [A-Za-z0-9._-] and must not be '.' or '..'.");
     process.exit(1);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nemoclaw.ts` around lines 2496 - 2499, Update the CLI error output in the
block that checks skillInstall.validateSkillName(skillName) to mention reserved
names in addition to the regex constraint: change the two console.error lines so
they state that names must match [A-Za-z0-9._-] and that the single-segment
reserved names "." and ".." are disallowed (reference
skillInstall.validateSkillName and the variable skillName to locate the check).
Ensure the message remains concise and printed before process.exit(1).
🧹 Nitpick comments (1)
src/nemoclaw.ts (1)

2710-2728: Deduplicate skill help output via printSkillInstallUsage().

The dispatcher reprints the same usage/help block already provided by printSkillInstallUsage(), which risks drift.

♻️ Suggested refactor
 async function sandboxSkillInstall(sandboxName, args = []) {
   const sub = args[0];
   if (!sub || sub === "help" || sub === "--help" || sub === "-h") {
-    console.log("");
-    console.log("  Usage: nemoclaw <sandbox> skill install <path>");
-    console.log("         nemoclaw <sandbox> skill remove <name>");
-    console.log("");
-    console.log("  Deploy or remove a skill in a running sandbox.");
-    console.log("");
-    console.log("  install <path>  Deploy a skill directory to the sandbox.");
-    console.log(
-      "    <path> must be a skill directory containing a SKILL.md (with 'name:' frontmatter),",
-    );
-    console.log(
-      "    or a direct path to a SKILL.md file. All non-dot files in the directory are uploaded.",
-    );
-    console.log("");
-    console.log("  remove <name>   Remove an installed skill from the sandbox by name.");
-    console.log("    <name> is the skill name from SKILL.md frontmatter (e.g. my-skill).");
-    console.log("");
+    printSkillInstallUsage();
     return;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nemoclaw.ts` around lines 2710 - 2728, The large duplicated help/usage
block for the "skill install/remove" command should be replaced by a single call
to the existing helper printSkillInstallUsage(); locate the conditional that
checks sub (the block starting with if (!sub || sub === "help" || sub ===
"--help" || sub === "-h")) and remove the console.log calls, then call
printSkillInstallUsage() instead so the help text is maintained in one place.
🤖 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/nemoclaw.ts`:
- Around line 2496-2499: Update the CLI error output in the block that checks
skillInstall.validateSkillName(skillName) to mention reserved names in addition
to the regex constraint: change the two console.error lines so they state that
names must match [A-Za-z0-9._-] and that the single-segment reserved names "."
and ".." are disallowed (reference skillInstall.validateSkillName and the
variable skillName to locate the check). Ensure the message remains concise and
printed before process.exit(1).

---

Nitpick comments:
In `@src/nemoclaw.ts`:
- Around line 2710-2728: The large duplicated help/usage block for the "skill
install/remove" command should be replaced by a single call to the existing
helper printSkillInstallUsage(); locate the conditional that checks sub (the
block starting with if (!sub || sub === "help" || sub === "--help" || sub ===
"-h")) and remove the console.log calls, then call printSkillInstallUsage()
instead so the help text is maintained in one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b7c91822-cb2c-42fb-aca7-3f37d704762c

📥 Commits

Reviewing files that changed from the base of the PR and between e1a7db7 and 8ea0d86.

📒 Files selected for processing (14)
  • nemoclaw-blueprint/policies/presets/telegram.yaml
  • nemoclaw/src/blueprint/runner.ts
  • nemoclaw/src/lib/subprocess-env.ts
  • package.json
  • src/lib/command-registry.ts
  • src/lib/config-io.ts
  • src/lib/http-probe.test.ts
  • src/lib/inventory-commands.ts
  • src/lib/skill-install.test.ts
  • src/lib/skill-install.ts
  • src/lib/validation.ts
  • src/nemoclaw.ts
  • test/check-docs-links.test.ts
  • test/credential-exposure.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/command-registry.ts
  • src/lib/skill-install.test.ts

@sinha-abhash sinha-abhash force-pushed the feat/skill-remove-command branch from 8ea0d86 to 8adfe48 Compare April 30, 2026 08:04
Sinha, Abhash and others added 8 commits April 30, 2026 10:07
Adds `nemoclaw <sandbox> skill remove <name>` to complement the
existing `skill install` command, providing a complete skill
lifecycle from the CLI.

- Removes the skill from the immutable upload dir:
  /sandbox/.openclaw/skills/<name>/
- Removes the OpenClaw mirror dir:
  $HOME/.openclaw/skills/<name>/
- Clears sessions.json so the agent re-discovers remaining skills
  on the next session (same mechanism as a fresh install)
- Verifies the directory is gone after removal
- Rejects invalid/unsafe skill names before any SSH operation
- Prints a clear error when the named skill is not installed

src/lib/skill-install.ts
- Updated module-level comment to mention skill remove
- Added removeSkill() - removes upload dir, mirror dir, clears sessions
- Added verifyRemove() - confirms directory is gone post-removal
- Added validateSkillName() - rejects shell metacharacters in CLI input

src/nemoclaw.ts
- Extended sandboxSkillInstall() to dispatch install vs remove
- Updated --help output to document both subcommands
- Updated global help text to list skill remove
- Validates skill name with validateSkillName() before any SSH ops
- Checks skill exists before attempting removal (clear error if missing)
- SSH config temp file always cleaned up in finally block

src/lib/skill-install.test.ts
- Added describe(validateSkillName) - valid names, empty, spaces, metacharacters
- Added describe(removeSkill) - SSH-failure path returns success=false + warning
- Added describe(verifyRemove) - SSH-failure returns false (conservative)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… parseFrontmatter

Both functions accepted single-dot and double-dot names because the
regex /^[A-Za-z0-9._-]+$/ allows '.'. In the remove path this would
expand to rm -rf '/sandbox/.openclaw/skills/..' which deletes the
parent .openclaw or skills/ directory instead of a single skill.

Fix: add explicit name !== '.' && name !== '..' guards in both
validateSkillName (CLI input) and parseFrontmatter (SKILL.md name
field), keeping the two checks aligned as CodeRabbit flagged.

Add test cases for '.' and '..' in both validateSkillName and
parseFrontmatter describe blocks. All 33 skill-install tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… OpenClaw mirror

- removeSkill(): success now requires both uploadDir and mirrorDir to be
  removed for OpenClaw sandboxes (previously only checked uploadDir, so
  a surviving mirror would still show the skill as removed)
- verifyRemove(): checks both uploadDir and mirrorDir via a compound SSH
  test before returning GONE (previously only checked uploadDir)
- checkExisting(): probes both uploadDir/SKILL.md and mirrorDir/SKILL.md
  so a partial removal (uploadDir gone, mirrorDir surviving) no longer
  blocks a reinstall

All 37 skill-install tests pass. Fixes address the Major issue flagged
by CodeRabbit on PR NVIDIA#1962.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move validateSkillName before parseFrontmatter so parseFrontmatter
can call it instead of duplicating the same dot/double-dot and regex
checks inline. Removes the second definition at the bottom of the file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extract skill remove logic into sandboxSkillRemove() and skill install
logic into sandboxSkillDeploy(). sandboxSkillInstall() is now a thin
dispatcher that handles help and routes to the two handlers.
Each function has a single responsibility.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Clarify error message for invalid skill names to mention '.' and '..'
- Change checkExisting() to return boolean|null (null = SSH probe failed)
  to distinguish absent from unreachable sandbox
- Update both callers: remove exits with clear error on null; install
  treats null as fresh install with a warning
- Fix verifyRemove failure message to say 'could not be verified' instead
  of asserting the upload dir still exists

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sshExec() can return a non-null result with status !== 0 when the SSH
transport fails (connection refused, auth error, etc.).  Previously,
checkExisting() only returned null when sshExec() itself returned null
(i.e. spawnSync threw), so a transport-layer failure was silently
collapsed into false — causing callers to report 'skill not installed'
instead of 'could not reach sandbox'.

Fix: treat result === null OR result.status !== 0 as a probe failure
and return null.  Also tighten the stdout check so any unexpected
output (neither EXISTS nor ABSENT) returns null rather than false.

Update the two unit tests that expected false on an unreachable SSH
config to expect null, matching the corrected contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…strictions

The error message in sandboxSkillRemove previously said only
'[A-Za-z0-9._-]' but validateSkillName also rejects '.' and '..'
as reserved names. Aligns the CLI error with the parseFrontmatter
error which already mentions both rules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@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 (1)
src/nemoclaw.ts (1)

2483-2737: Please run the sandbox lifecycle E2E set for this CLI-path change.

Given this PR updates src/nemoclaw.ts sandbox skill command flow, run the recommended targeted nightly jobs before merge:

  • sandbox-survival-e2e
  • sandbox-operations-e2e
  • skip-permissions-e2e

As per coding guidelines: src/nemoclaw.ts changes should run the listed E2E jobs via gh workflow run nightly-e2e.yaml --ref <branch> -f jobs=sandbox-survival-e2e,sandbox-operations-e2e,skip-permissions-e2e.

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

In `@src/nemoclaw.ts` around lines 2483 - 2737, The change touches the sandbox
skill CLI flow in src/nemoclaw.ts (see sandboxSkillRemove, sandboxSkillDeploy
and sandboxSkillInstall), so before merging run the targeted nightly E2E jobs:
invoke the GitHub Actions workflow nightly-e2e.yaml for this branch and pass the
three jobs sandbox-survival-e2e,sandbox-operations-e2e,skip-permissions-e2e
(e.g. use gh workflow run nightly-e2e.yaml --ref <branch> -f
jobs=sandbox-survival-e2e,sandbox-operations-e2e,skip-permissions-e2e) and
attach the run results to the PR.
🤖 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/skill-install.test.ts`:
- Around line 84-87: The test in skill-install.test.ts that checks
parseFrontmatter should be updated to match the parser's current error message:
change the expect(...).toThrow("invalid characters") assertions in the "rejects
dot and double-dot as skill names in frontmatter" test to expect the new message
thrown by parseFrontmatter ("is invalid. Use [A-Za-z0-9._-] and do not use '.'
or '..'."); update both cases (name: . and name: ..) to assert that exact string
from parseFrontmatter.

In `@src/nemoclaw.ts`:
- Around line 2496-2499: The validation error message for invalid skill names is
misleading because validateSkillName(skillName) also rejects the reserved names
"." and ".."; update the error output around the validateSkillName check to
mention that names must match the allowed pattern [A-Za-z0-9._-] and that the
single-dot "." and double-dot ".." are reserved/invalid (i.e., change the
console.error lines that reference skillName to include an explicit note that
"." and ".." are not allowed).

---

Nitpick comments:
In `@src/nemoclaw.ts`:
- Around line 2483-2737: The change touches the sandbox skill CLI flow in
src/nemoclaw.ts (see sandboxSkillRemove, sandboxSkillDeploy and
sandboxSkillInstall), so before merging run the targeted nightly E2E jobs:
invoke the GitHub Actions workflow nightly-e2e.yaml for this branch and pass the
three jobs sandbox-survival-e2e,sandbox-operations-e2e,skip-permissions-e2e
(e.g. use gh workflow run nightly-e2e.yaml --ref <branch> -f
jobs=sandbox-survival-e2e,sandbox-operations-e2e,skip-permissions-e2e) and
attach the run results to the PR.
🪄 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: 1be63b75-1ccd-4fc7-ac19-1d943b776a1c

📥 Commits

Reviewing files that changed from the base of the PR and between 8ea0d86 and 8adfe48.

📒 Files selected for processing (3)
  • src/lib/skill-install.test.ts
  • src/lib/skill-install.ts
  • src/nemoclaw.ts

Comment thread src/lib/skill-install.test.ts
Comment thread src/nemoclaw.ts Outdated

@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

🧹 Nitpick comments (1)
src/nemoclaw.ts (1)

2695-2771: Please run the recommended sandbox lifecycle E2Es on this new command path.

skill remove now drives live sandbox SSH, remote deletion, session refresh, and reconnect-on-next-session behavior. I'd want the recommended sandbox-survival-e2e and sandbox-operations-e2e coverage on this PR before merge.

As per coding guidelines src/nemoclaw.ts: “Changes affect sandbox lifecycle commands. E2E test recommendation: sandbox-survival-e2e — gateway restart recovery; sandbox-operations-e2e — process recovery after gateway kill.”

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

In `@src/nemoclaw.ts` around lines 2695 - 2771, The new sandboxSkillRemove
implementation in src/nemoclaw.ts modifies live sandbox lifecycle (SSH removal,
session refresh, reconnect behavior) but lacks E2E verification; run the
recommended E2Es (sandbox-survival-e2e and sandbox-operations-e2e) exercising
the new command path (sandboxSkillRemove / "sandbox <name> skill remove <name>")
and update or add tests to cover SSH-driven remote deletion, session
refresh/reconnect-on-next-session, and gateway restart/process-kill recovery; if
any test fails, adjust sandboxSkillRemove or its helpers
(agentRuntime.getSessionAgent, skillInstall.removeSkill, verifyRemove, and
ensureLiveSandboxOrExit usage) until the E2Es pass.
🤖 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/skill-install.ts`:
- Around line 330-335: The SkillPaths type is missing the mirrorDir property and
resolveSkillPaths() never sets it, causing the accesses in checkExisting, and
other places (uses of paths.mirrorDir) to be invalid; add mirrorDir?: string to
the SkillPaths interface and modify resolveSkillPaths() to compute and assign
mirrorDir (e.g., when isOpenClaw is true or when a mirror path is present) so
that functions like checkExisting and any callers safely read paths.mirrorDir
(ensure it is optional or set to undefined when not applicable).

---

Nitpick comments:
In `@src/nemoclaw.ts`:
- Around line 2695-2771: The new sandboxSkillRemove implementation in
src/nemoclaw.ts modifies live sandbox lifecycle (SSH removal, session refresh,
reconnect behavior) but lacks E2E verification; run the recommended E2Es
(sandbox-survival-e2e and sandbox-operations-e2e) exercising the new command
path (sandboxSkillRemove / "sandbox <name> skill remove <name>") and update or
add tests to cover SSH-driven remote deletion, session
refresh/reconnect-on-next-session, and gateway restart/process-kill recovery; if
any test fails, adjust sandboxSkillRemove or its helpers
(agentRuntime.getSessionAgent, skillInstall.removeSkill, verifyRemove, and
ensureLiveSandboxOrExit usage) until the E2Es pass.
🪄 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: 45a6b89f-8131-4b44-b32d-509b09e99fec

📥 Commits

Reviewing files that changed from the base of the PR and between 8adfe48 and abc2d87.

📒 Files selected for processing (14)
  • nemoclaw-blueprint/policies/presets/telegram.yaml
  • nemoclaw/src/blueprint/runner.ts
  • nemoclaw/src/lib/subprocess-env.ts
  • package.json
  • src/lib/command-registry.ts
  • src/lib/config-io.ts
  • src/lib/http-probe.test.ts
  • src/lib/inventory-commands.ts
  • src/lib/skill-install.test.ts
  • src/lib/skill-install.ts
  • src/lib/validation.ts
  • src/nemoclaw.ts
  • test/check-docs-links.test.ts
  • test/credential-exposure.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/skill-install.test.ts

Comment thread src/lib/skill-install.ts
…hs()

SkillPaths was missing the mirrorDir property, causing TypeScript to flag
accesses to paths.mirrorDir in checkExisting(), removeSkill(), and
verifyRemove() as undefined property references.

- Add mirrorDir: string | null to SkillPaths interface
- Populate it in resolveSkillPaths() as
  isOpenClaw ? '$HOME/.openclaw/skills/<name>' : null
- Add mirrorDir assertions to resolveSkillPaths() unit tests
- Update stale 'invalid characters' assertions in parseFrontmatter
  tests to 'is invalid' to match the updated error message

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@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 `@src/lib/skill-install.test.ts`:
- Around line 334-339: The non-OpenClaw tests use the wrong agent config shape:
update the resolveSkillPaths/resolvePaths call sites in the tests to pass the
current agent shape by replacing configPaths.immutableDir and
configPaths.writableDir with a single configPaths.dir (e.g., configPaths: { dir:
"/sandbox/.hermes" }) so resolveSkillPaths/resolvePaths computes the correct
skill paths; apply this same change to the three occurrences around the failing
assertions (the calls that currently pass immutableDir/writableDir).
🪄 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: 4baf4ffa-8edd-4886-af07-0edb45e9f8f2

📥 Commits

Reviewing files that changed from the base of the PR and between abc2d87 and 3009874.

📒 Files selected for processing (2)
  • src/lib/skill-install.test.ts
  • src/lib/skill-install.ts

Comment thread src/lib/skill-install.test.ts
…pers

resolveSkillPaths() reads configPaths.dir, but the three non-OpenClaw
test call sites were passing configPaths.immutableDir/writableDir (the
old agent shape). This caused the computed uploadDir to be
'undefined/skills/test-skill', weakening what the tests validate.

Replace all three occurrences with configPaths: { dir: '/sandbox/.hermes' }.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sinha-abhash

Copy link
Copy Markdown
Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Reviews resumed.

@cv cv added the v0.0.51 Release target label May 23, 2026
@cv

cv commented May 25, 2026

Copy link
Copy Markdown
Collaborator

@sinha-abhash could you add a DCO to this PR, please? We can't merge it otherwise.

@cv cv added v0.0.52 Release target v0.0.53 Release target and removed v0.0.51 Release target v0.0.52 Release target labels May 26, 2026
@ericksoa ericksoa added v0.0.55 and removed v0.0.53 Release target labels May 27, 2026
@cv cv added v0.0.56 Release target and removed v0.0.55 labels May 29, 2026
@cv

cv commented May 30, 2026

Copy link
Copy Markdown
Collaborator

@sinha-abhash can you add a DCO, please?

@cv

cv commented May 30, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the contribution. Since this branch remains DCO-blocked, I opened a maintainer-signed salvage PR with the same feature work here: #4563. We'll continue review on the replacement PR.

@cv

cv commented May 30, 2026

Copy link
Copy Markdown
Collaborator

Closing as superseded by the maintainer-signed salvage PR: #4563.

@cv cv closed this May 30, 2026
cv added a commit that referenced this pull request May 30, 2026
## Summary
Salvages the skill removal work from
[#1962](#1962) onto a
maintainer-owned, signed-off branch so the DCO-blocked contribution can
proceed.
Adds `nemoclaw <sandbox> skill remove <skill>` for removing installed
agent skills from a running sandbox.

## Related Issue
Supersedes [#1962](#1962).

## Changes
- Add the native `sandbox skill remove` oclif command and public argv
translation.
- Add removal logic for upload directories, OpenClaw mirror directories,
and OpenClaw session refresh.
- Validate skill names before constructing remote removal paths,
including rejecting `.` and `..`.
- Update command reference docs and CLI display metadata.
- Add unit and command tests for remove behavior and safety validation.

## Type of Change

- [x] Code change with doc updates
- [ ] Code change (feature, bug fix, or refactor)
- [ ] Doc only (prose changes, no code sample modifications)
- [ ] Doc only (includes code sample changes)

## Verification
- [ ] `npx prek run --all-files` passes
- [x] `npm test` passes
- [x] Tests added or updated for new or changed behavior
- [x] No secrets, API keys, or credentials committed
- [x] Docs updated for user-facing behavior changes
- [ ] `npm run docs` builds without warnings (doc changes only)
- [x] 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)

Validation run:
- `npm run build:cli`
- `npm test -- src/lib/skill-install.test.ts
src/commands/sandbox/skill.test.ts
src/lib/cli/public-argv-translation.test.ts
src/lib/cli/command-registry.test.ts`
- `npm run typecheck:cli`
- `npm run docs:strict`
- commit/push hooks completed successfully, including CLI test and
pre-push TypeScript checks.
- `npm run docs:strict` completed with no errors (Fern reported existing
upgrade warnings).

---
Signed-off-by: Carlos Villela <cvillela@nvidia.com>


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

* **New Features**
* Added a CLI command to remove an installed skill from a running
sandbox by SKILL.md name, with validation and verification.

* **Documentation**
* CLI reference, help text, usage and examples updated to document the
remove subcommand and agent-specific behavior (non-OpenClaw agents may
require gateway restart).

* **Tests**
* Test suites expanded to cover removal flows, validation, and error
cases.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/NVIDIA/NemoClaw/pull/4563?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Carlos Villela <cvillela@nvidia.com>
@wscurran wscurran added area: cli Command line interface, flags, terminal UX, or output feature PR adds or expands user-visible functionality and removed NemoClaw CLI 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 v0.0.56 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants