Skip to content

feat(policy): support custom preset files via --from-file / --from-dir (closes #2039)#2077

Open
KimBioInfoStudio wants to merge 1 commit intoNVIDIA:mainfrom
KimBioInfoStudio:feat/2039-policy-add-from-file
Open

feat(policy): support custom preset files via --from-file / --from-dir (closes #2039)#2077
KimBioInfoStudio wants to merge 1 commit intoNVIDIA:mainfrom
KimBioInfoStudio:feat/2039-policy-add-from-file

Conversation

@KimBioInfoStudio
Copy link
Copy Markdown

@KimBioInfoStudio KimBioInfoStudio commented Apr 19, 2026

Summary

Lets users apply user-authored preset YAML to a running sandbox without editing the baseline or dropping to openshell policy set. Adds --from-file, --from-dir, --yes/-y, and NEMOCLAW_NON_INTERACTIVE=1 support to nemoclaw <name> policy-add.

Related Issue

Closes #2039

Changes

  • src/nemoclaw.ts: extend sandboxPolicyAdd with --from-file <path>, --from-dir <path>, --yes/-y, and NEMOCLAW_NON_INTERACTIVE=1 handling; new applyExternalPreset helper; updated help text.
  • src/lib/policies.ts: new loadPresetFromFile (validates path, YAML, preset.name as RFC 1123 label, presence of network_policies, rejects collisions with built-in preset names); refactor applyPreset to delegate to a new applyPresetContent that works on raw content so the external path can reuse the merge + policy-set logic.
  • test/policies.test.ts: unit tests for loadPresetFromFile (valid/invalid/missing fields/name collision) and integration tests for the --from-file / --from-dir CLI paths (success, dry-run, non-interactive env, mutually exclusive flags, missing path, abort-on-first-failure).
  • docs/network-policy/customize-network-policy.md: new Custom Preset Files section with YAML skeleton, --from-file / --from-dir usage, and a warning that external hosts bypass review.
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md: regenerated from docs via scripts/docs-to-skills.py.

Design decisions

  • Name collision: reject any custom preset whose preset.name matches a built-in preset — the user must rename it. Keeps the preset registry unambiguous.
  • --from-dir failure mode: stop at the first failure and leave earlier, successfully-applied presets in place. Rollback would require deleting already-merged entries from the running sandbox policy, which is risky; let the user fix the failing file and re-run.
  • Custom hosts: trust the user and print a warning rather than SSRF-gate. Documented in the new section of customize-network-policy.md.

Type of Change

  • Code change with doc updates

Verification

  • npx prek run --all-files passes
  • npm test passes (policies project: 95/95; install-preflight project: 57/57)
  • 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: Claude Code

Signed-off-by: Kim Yang kimy@nvidia.com

Summary by CodeRabbit

  • New Features

    • Apply user-authored preset YAMLs via --from-file and bulk via --from-dir (files processed lexicographically; stops on first failure; no rollback).
    • Non-interactive controls: --yes/-y or NEMOCLAW_NON_INTERACTIVE=1; preview with --dry-run; preview shows endpoints and a warning for unvetted targets.
  • Documentation

    • Added guide for custom preset structure, name/validation rules, collision behavior, CLI usage, and egress/review warning.
  • Tests

    • Coverage for file/dir workflows, validations, dry-run, prompts, and abort semantics.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 19, 2026

📝 Walkthrough

Walkthrough

Adds CLI support to apply user-authored preset YAMLs via nemoclaw <sandbox> policy-add --from-file/--from-dir, implements loading/validation/apply helpers in src/lib/policies.ts, updates interactive/non-interactive flows and help text, and adds docs and tests covering these flows. (≤50 words)

Changes

Cohort / File(s) Summary
Documentation
.agents/skills/nemoclaw-user-manage-policy/SKILL.md, docs/network-policy/customize-network-policy.md
New "Custom Preset Files" sections describing required YAML shape (preset.name as a lowercase RFC 1123-like label), built-in-name collision rules, CLI workflows (--from-file, --from-dir), flags (--dry-run, --yes, NEMOCLAW_NON_INTERACTIVE=1), lexicographic dir processing, stop-on-first-failure semantics, and a warning that custom preset hosts bypass review.
Policy library
src/lib/policies.ts
Refactored preset application: added exported applyPresetContent(sandboxName, presetName, presetContent, options) and loadPresetFromFile(filePath) (validates extension, YAML, preset.name, network_policies, and collisions with built-ins); applyPreset() now loads built-in YAML and delegates to applyPresetContent. Minor simplification in removePresetFromPolicy.
CLI
src/nemoclaw.ts
sandboxPolicyAdd() extended for mutually-exclusive --from-file / --from-dir and non-interactive --yes / NEMOCLAW_NON_INTERACTIVE; added applyExternalPreset(...) helper that loads/validates, previews endpoints, warns about unvetted hosts, honors --dry-run, prompts unless bypassed, and sequentially applies directory entries (lexicographic, abort on first failure). Help text updated.
Tests
test/policies.test.ts
Adjusted buildPolicySetCommand expectations; added unit tests for loadPresetFromFile() (success and multiple failure modes: missing file, bad ext, invalid YAML, missing/invalid preset.name, missing network_policies, collision with built-in); added integration-style tests for policy-add --from-file/--from-dir covering apply, dry-run, prompts, non-interactive mode, arg validation, sorted iteration, and abort-on-failure behavior.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as "nemoclaw CLI"
  participant FS as Filesystem
  participant Policies as policies.ts
  participant OpenShell as openshell

  User->>CLI: run "nemoclaw <sandbox> policy-add --from-file /path/preset.yaml [--dry-run] [--yes]"
  CLI->>FS: read /path/preset.yaml
  FS-->>CLI: file contents
  CLI->>Policies: loadPresetFromFile(filePath)
  Policies-->>CLI: { presetName, content } or error
  alt load success
    CLI->>CLI: show endpoint preview & warning
    alt dry-run
      CLI-->>User: preview only (no apply)
    else apply
      CLI->>Policies: applyPresetContent(sandbox, presetName, content, options)
      Policies->>OpenShell: "openshell policy set --wait" (merge network_policies)
      OpenShell-->>Policies: success / failure
      Policies-->>CLI: result
      CLI-->>User: success / error
    end
  else load failure
    CLI-->>User: error & exit non-zero
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A preset hopped from file to shell,
No more manual merges in the dell,
Validate, preview, then apply—
--yes skips the questions sly,
Custom hosts warned, hop safe, farewell! 🎩

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% 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 clearly and concisely summarizes the main feature addition: support for custom preset files via new CLI flags --from-file and --from-dir.
Linked Issues check ✅ Passed All coding requirements from #2039 are met: --from-file/--from-dir support, preset validation (YAML, name format, collision detection), dry-run support, non-interactive flags, and directory processing semantics.
Out of Scope Changes check ✅ Passed All changes directly support the linked issue objectives: policy handling refactoring, CLI flag additions, preset validation, documentation updates, and comprehensive test coverage for the new feature.

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

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


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

@KimBioInfoStudio KimBioInfoStudio force-pushed the feat/2039-policy-add-from-file branch from 9e08ba8 to 6ce9ecb Compare April 20, 2026 02:44
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: 1

🧹 Nitpick comments (1)
test/policies.test.ts (1)

1120-1164: Make prompt response configurable to cover external decline paths.

runPolicyAddExternal hardcodes prompt return value to "y", so the interactive decline behavior for external presets is untestable. Consider parameterizing it and adding a "no" case to prevent regressions around confirmation safety.

Possible refactor
-    function runPolicyAddExternal(extraArgs = [], envOverrides = {}) {
+    function runPolicyAddExternal(extraArgs = [], envOverrides = {}, promptAnswer = "y") {
...
-credentials.prompt = async (message) => {
+credentials.prompt = async (message) => {
   calls.push({ type: "prompt", message });
-  return "y";
+  return ${JSON.stringify("PROMPT_SENTINEL")};
 };
...
-      fs.writeFileSync(scriptPath, script);
+      fs.writeFileSync(scriptPath, script.replace("PROMPT_SENTINEL", promptAnswer));
+    it("does not apply when external preset confirmation is declined with 'no'", () => {
+      const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-from-file-no-"));
+      const file = path.join(tmp, "custom-rule.yaml");
+      fs.writeFileSync(file, "preset:\n  name: custom-rule\nnetwork_policies: {}\n");
+      const result = runPolicyAddExternal(["--from-file", file], {}, "no");
+      expect(result.status).toBe(0);
+      const calls = JSON.parse(result.stdout.split("__CALLS__")[1].trim());
+      expect(calls.some((c) => c.type === "apply")).toBeFalsy();
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/policies.test.ts` around lines 1120 - 1164, runPolicyAddExternal
currently hardcodes the interactive prompt response by setting policies.prompt =
async (...) => "y", which prevents testing the decline path; change
runPolicyAddExternal to accept a promptResponse parameter (default "y"), pass
that into the generated script and set policies.prompt = async (message) => {
calls.push({ type: "prompt", message }); return promptResponse; }, update any
callers in tests to invoke runPolicyAddExternal(..., ..., "no") to cover the
decline path and assert behavior based on the calls array and process output;
keep existing extraArgs and envOverrides parameters intact so other tests remain
unaffected.
🤖 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 1539-1542: The confirmation logic incorrectly treats only the
exact string "n" as a decline, so inputs like "no" are accepted; update both
confirmation branches (the block using askPrompt and the similar block around
the code handling the alternative at lines ~1572-1577) to normalize the response
with trim() and toLowerCase() and treat any response starting with "n" (e.g.,
response.toLowerCase().trim().startsWith("n")) as a decline; reference the
askPrompt call and the local yes variable to locate and replace the current
exact-equality checks.

---

Nitpick comments:
In `@test/policies.test.ts`:
- Around line 1120-1164: runPolicyAddExternal currently hardcodes the
interactive prompt response by setting policies.prompt = async (...) => "y",
which prevents testing the decline path; change runPolicyAddExternal to accept a
promptResponse parameter (default "y"), pass that into the generated script and
set policies.prompt = async (message) => { calls.push({ type: "prompt", message
}); return promptResponse; }, update any callers in tests to invoke
runPolicyAddExternal(..., ..., "no") to cover the decline path and assert
behavior based on the calls array and process output; keep existing extraArgs
and envOverrides parameters intact so other tests remain unaffected.
🪄 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: 4f840bb0-6819-4a75-892e-de82dbe3ff7e

📥 Commits

Reviewing files that changed from the base of the PR and between 9e08ba8 and 6ce9ecb.

📒 Files selected for processing (5)
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
  • docs/network-policy/customize-network-policy.md
  • src/lib/policies.ts
  • src/nemoclaw.ts
  • test/policies.test.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/network-policy/customize-network-policy.md
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/policies.ts

Comment thread src/nemoclaw.ts
@wscurran wscurran added NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. labels Apr 20, 2026
@KimBioInfoStudio KimBioInfoStudio force-pushed the feat/2039-policy-add-from-file branch from 6ce9ecb to 14ca974 Compare April 20, 2026 06:11
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: 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/policies.ts`:
- Around line 519-536: In loadPresetFromFile, fs.readFileSync is called outside
the try-catch which allows a TOCTOU read error to escape; fix by declaring let
content; (and keeping parsed declared) then move the call to
fs.readFileSync(abs, "utf-8") inside the try block before calling
YAML.parse(content), catch any error and call console.error with a descriptive
message (including err.message) and return null so all read/parse failures
follow the same graceful null-return pattern.
🪄 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: cc1781f7-bdb1-42ab-8f84-88dfa7b2d333

📥 Commits

Reviewing files that changed from the base of the PR and between 6ce9ecb and 14ca974.

📒 Files selected for processing (5)
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
  • docs/network-policy/customize-network-policy.md
  • src/lib/policies.ts
  • src/nemoclaw.ts
  • test/policies.test.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/network-policy/customize-network-policy.md

Comment thread src/lib/policies.ts
Comment on lines +519 to +536
function loadPresetFromFile(filePath) {
const abs = path.resolve(filePath);
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
console.error(` Preset file not found: ${filePath}`);
return null;
}
if (!/\.ya?ml$/i.test(abs)) {
console.error(` Preset file must be .yaml or .yml: ${filePath}`);
return null;
}
const content = fs.readFileSync(abs, "utf-8");
let parsed;
try {
parsed = YAML.parse(content);
} catch (err) {
console.error(` Invalid YAML in ${filePath}: ${err.message}`);
return null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Uncaught exception if file becomes unreadable between existence check and read.

fs.readFileSync at line 529 is outside the try-catch block, so if the file is deleted or permissions change after the fs.existsSync check (line 521) but before the read, the exception propagates uncaught. While this is a narrow TOCTOU window in a CLI context, it's inconsistent with the graceful null-return pattern used for other failure cases.

Proposed fix to wrap readFileSync
   if (!/\.ya?ml$/i.test(abs)) {
     console.error(`  Preset file must be .yaml or .yml: ${filePath}`);
     return null;
   }
-  const content = fs.readFileSync(abs, "utf-8");
   let parsed;
   try {
+    const content = fs.readFileSync(abs, "utf-8");
     parsed = YAML.parse(content);
   } catch (err) {
-    console.error(`  Invalid YAML in ${filePath}: ${err.message}`);
+    const msg = err.code === "ENOENT" || err.code === "EACCES"
+      ? `Cannot read ${filePath}: ${err.message}`
+      : `Invalid YAML in ${filePath}: ${err.message}`;
+    console.error(`  ${msg}`);
     return null;
   }

Note: You'll need to move const content inside the try block and keep it in scope for the return statement by storing it alongside parsed, or restructure slightly.

📝 Committable suggestion

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

Suggested change
function loadPresetFromFile(filePath) {
const abs = path.resolve(filePath);
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
console.error(` Preset file not found: ${filePath}`);
return null;
}
if (!/\.ya?ml$/i.test(abs)) {
console.error(` Preset file must be .yaml or .yml: ${filePath}`);
return null;
}
const content = fs.readFileSync(abs, "utf-8");
let parsed;
try {
parsed = YAML.parse(content);
} catch (err) {
console.error(` Invalid YAML in ${filePath}: ${err.message}`);
return null;
}
function loadPresetFromFile(filePath) {
const abs = path.resolve(filePath);
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
console.error(` Preset file not found: ${filePath}`);
return null;
}
if (!/\.ya?ml$/i.test(abs)) {
console.error(` Preset file must be .yaml or .yml: ${filePath}`);
return null;
}
let parsed;
try {
const content = fs.readFileSync(abs, "utf-8");
parsed = YAML.parse(content);
} catch (err) {
const msg = err.code === "ENOENT" || err.code === "EACCES"
? `Cannot read ${filePath}: ${err.message}`
: `Invalid YAML in ${filePath}: ${err.message}`;
console.error(` ${msg}`);
return null;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/policies.ts` around lines 519 - 536, In loadPresetFromFile,
fs.readFileSync is called outside the try-catch which allows a TOCTOU read error
to escape; fix by declaring let content; (and keeping parsed declared) then move
the call to fs.readFileSync(abs, "utf-8") inside the try block before calling
YAML.parse(content), catch any error and call console.error with a descriptive
message (including err.message) and return null so all read/parse failures
follow the same graceful null-return pattern.

Lets users apply user-authored preset YAML to a running sandbox without
editing the baseline policy or dropping to openshell policy set.

- --from-file <path> applies a single custom preset
- --from-dir <path> applies every .yaml preset in a directory; stops at the
  first failure and does not roll back already-applied presets
- --yes / -y and NEMOCLAW_NON_INTERACTIVE=1 skip the confirmation prompt,
  also covering the original built-in preset path
- Custom presets must declare preset.name as an RFC 1123 label that does
  not collide with a built-in preset; the file must include a
  network_policies section
- Custom hosts are not vetted and a warning is printed before apply; the
  docs page spells out the risk

Closes NVIDIA#2039

Signed-off-by: Kim Yang <kimy@nvidia.com>
@KimBioInfoStudio KimBioInfoStudio force-pushed the feat/2039-policy-add-from-file branch from 14ca974 to 5ba18f5 Compare April 20, 2026 09:16
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/nemoclaw.ts (1)

1534-1536: ⚠️ Potential issue | 🟠 Major

--dry-run never reaches the diff/merge path.

Both dry-run branches return before policies.applyPreset(...) / policies.applyPresetContent(...) runs, so the command only prints endpoint summaries and never shows the merged policy diff promised by this feature. That leaves operators unable to validate the actual policy change before applying a custom preset.

Also applies to: 1567-1569

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

In `@src/nemoclaw.ts` around lines 1534 - 1536, The dryRun branch returns early
and thus never invokes policies.applyPreset or policies.applyPresetContent, so
the tool only prints endpoint summaries and never computes/shows the merged
policy diff — remove the early return and instead call the same
applyPreset/applyPresetContent logic in dry-run mode but avoid persisting
changes; specifically, when dryRun is true, call policies.applyPreset(...) or
policies.applyPresetContent(...), capture the merged/updated policy result and
render the diff/merge output (same code used for the real path), but skip any
persistence/commit step; apply this change to both occurrences referencing
dryRun around policies.applyPreset and policies.applyPresetContent so dry-run
shows the merged diff without applying it.
🤖 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 2394-2396: The help text currently claims `--yes /
NEMOCLAW_NON_INTERACTIVE=1 skip prompts`, which is inaccurate; update the string
in the CLI help template that contains `${D}--yes / -y or
NEMOCLAW_NON_INTERACTIVE=1 skip prompts${R}` so it clarifies that `--yes` (or
the env var) only skips the final confirmation prompt and does not bypass the
preset-selection flow — note that preset selection remains interactive unless
`--from-file` or `--from-dir` is supplied; make the wording concise like “skip
confirmation prompt; preset selection remains interactive unless --from-file or
--from-dir is used.”

In `@test/policies.test.ts`:
- Around line 276-286: The tests calling policies.buildPolicySetCommand assume
NEMOCLAW_OPENSHELL_BIN is unset and delete it in finally, making them
non-hermetic; modify the test(s) to capture the current value of
process.env.NEMOCLAW_OPENSHELL_BIN at the start (e.g., const prior =
process.env.NEMOCLAW_OPENSHELL_BIN), then set or delete the env var for the test
case as needed, and in the finally block restore it
(process.env.NEMOCLAW_OPENSHELL_BIN = prior === undefined ? undefined : prior)
so the original environment is preserved; apply the same pattern to the other
argv test(s) that manipulate NEMOCLAW_OPENSHELL_BIN and reference
policies.buildPolicySetCommand to locate the affected tests.

---

Outside diff comments:
In `@src/nemoclaw.ts`:
- Around line 1534-1536: The dryRun branch returns early and thus never invokes
policies.applyPreset or policies.applyPresetContent, so the tool only prints
endpoint summaries and never computes/shows the merged policy diff — remove the
early return and instead call the same applyPreset/applyPresetContent logic in
dry-run mode but avoid persisting changes; specifically, when dryRun is true,
call policies.applyPreset(...) or policies.applyPresetContent(...), capture the
merged/updated policy result and render the diff/merge output (same code used
for the real path), but skip any persistence/commit step; apply this change to
both occurrences referencing dryRun around policies.applyPreset and
policies.applyPresetContent so dry-run shows the merged diff without applying
it.
🪄 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: fa3bcdb8-b5ae-4425-89d6-31dc821775e3

📥 Commits

Reviewing files that changed from the base of the PR and between 14ca974 and 5ba18f5.

📒 Files selected for processing (5)
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
  • docs/network-policy/customize-network-policy.md
  • src/lib/policies.ts
  • src/nemoclaw.ts
  • test/policies.test.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/network-policy/customize-network-policy.md
  • .agents/skills/nemoclaw-user-manage-policy/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/policies.ts

Comment thread src/nemoclaw.ts
Comment on lines +2394 to +2396
${D}--from-file <path> apply a custom preset YAML${R}
${D}--from-dir <path> apply every .yaml preset in a directory${R}
${D}--yes / -y or NEMOCLAW_NON_INTERACTIVE=1 skip prompts${R}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Help text overstates the non-interactive behavior.

--yes / NEMOCLAW_NON_INTERACTIVE=1 only skips the confirmation step here; the default preset-selection flow still prompts unless --from-file or --from-dir is supplied. Saying “skip prompts” is misleading and can send automation into a hang.

✏️ Suggested wording
-                                     ${D}--yes / -y or NEMOCLAW_NON_INTERACTIVE=1 skip prompts${R}
+                                     ${D}--yes / -y or NEMOCLAW_NON_INTERACTIVE=1 skip confirmations${R}
📝 Committable suggestion

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

Suggested change
${D}--from-file <path> apply a custom preset YAML${R}
${D}--from-dir <path> apply every .yaml preset in a directory${R}
${D}--yes / -y or NEMOCLAW_NON_INTERACTIVE=1 skip prompts${R}
${D}--from-file <path> apply a custom preset YAML${R}
${D}--from-dir <path> apply every .yaml preset in a directory${R}
${D}--yes / -y or NEMOCLAW_NON_INTERACTIVE=1 skip confirmations${R}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nemoclaw.ts` around lines 2394 - 2396, The help text currently claims
`--yes / NEMOCLAW_NON_INTERACTIVE=1 skip prompts`, which is inaccurate; update
the string in the CLI help template that contains `${D}--yes / -y or
NEMOCLAW_NON_INTERACTIVE=1 skip prompts${R}` so it clarifies that `--yes` (or
the env var) only skips the final confirmation prompt and does not bypass the
preset-selection flow — note that preset selection remains interactive unless
`--from-file` or `--from-dir` is supplied; make the wording concise like “skip
confirmation prompt; preset selection remains interactive unless --from-file or
--from-dir is used.”

Comment thread test/policies.test.ts
Comment on lines 276 to +286
it("returns an argv array with sandbox name as a separate element", () => {
const cmd = policies.buildPolicySetCommand("/tmp/policy.yaml", "my-assistant");
expect(cmd).toEqual(["openshell", "policy", "set", "--policy", "/tmp/policy.yaml", "--wait", "my-assistant"]);
expect(cmd).toEqual([
"openshell",
"policy",
"set",
"--policy",
"/tmp/policy.yaml",
"--wait",
"my-assistant",
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Make these argv tests restore NEMOCLAW_OPENSHELL_BIN instead of assuming it is unset.

The first case depends on the ambient environment not defining NEMOCLAW_OPENSHELL_BIN, and the second case always deletes that variable in finally instead of restoring any preexisting value. That makes the suite non-hermetic and can cause order- or environment-dependent failures.

🧪 Suggested fix
     it("returns an argv array with sandbox name as a separate element", () => {
-      const cmd = policies.buildPolicySetCommand("/tmp/policy.yaml", "my-assistant");
-      expect(cmd).toEqual([
-        "openshell",
-        "policy",
-        "set",
-        "--policy",
-        "/tmp/policy.yaml",
-        "--wait",
-        "my-assistant",
-      ]);
+      const originalBin = process.env.NEMOCLAW_OPENSHELL_BIN;
+      delete process.env.NEMOCLAW_OPENSHELL_BIN;
+      try {
+        const cmd = policies.buildPolicySetCommand("/tmp/policy.yaml", "my-assistant");
+        expect(cmd).toEqual([
+          "openshell",
+          "policy",
+          "set",
+          "--policy",
+          "/tmp/policy.yaml",
+          "--wait",
+          "my-assistant",
+        ]);
+      } finally {
+        if (originalBin === undefined) delete process.env.NEMOCLAW_OPENSHELL_BIN;
+        else process.env.NEMOCLAW_OPENSHELL_BIN = originalBin;
+      }
     });
…
     it("uses the resolved openshell binary when provided by the installer path", () => {
-      process.env.NEMOCLAW_OPENSHELL_BIN = "/tmp/fake path/openshell";
+      const originalBin = process.env.NEMOCLAW_OPENSHELL_BIN;
+      process.env.NEMOCLAW_OPENSHELL_BIN = "/tmp/fake path/openshell";
       try {
         const cmd = policies.buildPolicySetCommand("/tmp/policy.yaml", "my-assistant");
         expect(cmd).toEqual([
           "/tmp/fake path/openshell",
           "policy",
           "set",
           "--policy",
           "/tmp/policy.yaml",
           "--wait",
           "my-assistant",
         ]);
       } finally {
-        delete process.env.NEMOCLAW_OPENSHELL_BIN;
+        if (originalBin === undefined) delete process.env.NEMOCLAW_OPENSHELL_BIN;
+        else process.env.NEMOCLAW_OPENSHELL_BIN = originalBin;
       }
     });

Also applies to: 302-316

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

In `@test/policies.test.ts` around lines 276 - 286, The tests calling
policies.buildPolicySetCommand assume NEMOCLAW_OPENSHELL_BIN is unset and delete
it in finally, making them non-hermetic; modify the test(s) to capture the
current value of process.env.NEMOCLAW_OPENSHELL_BIN at the start (e.g., const
prior = process.env.NEMOCLAW_OPENSHELL_BIN), then set or delete the env var for
the test case as needed, and in the finally block restore it
(process.env.NEMOCLAW_OPENSHELL_BIN = prior === undefined ? undefined : prior)
so the original environment is preserved; apply the same pattern to the other
argv test(s) that manipulate NEMOCLAW_OPENSHELL_BIN and reference
policies.buildPolicySetCommand to locate the affected tests.

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

Labels

enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NemoClaw][CLI] nemoclaw policy-add has no custom-preset surface; custom egress targets require dropping to openshell policy set

2 participants