fix(onboard): pre-select suggested policy presets in visual display#777
Conversation
📝 WalkthroughWalkthroughThe selection indicator for policy presets in the interactive listing now displays suggested presets as selected alongside already-applied presets. This updates the visual status indicator so that presets marked as suggested appear selected before actual application, aligning the UI with the intended behavior. Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
prekshivyas
left a comment
There was a problem hiding this comment.
Clean one-liner, all CI green. LGTM.
|
Hey @temrjan — merging is blocked because commit 6c1434c ("fix(onboard): pre-select suggested policy presets in visual display") isn't signed. The repo requires verified signatures on all commits. Suggestion: Key thing: Make sure the signing key (SSH or GPG) is added to your GitHub account under Settings → SSH and GPG keys so GitHub can verify it. |
62866a3 to
a0bc8ca
Compare
There was a problem hiding this comment.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
bin/lib/nim.js (1)
191-205:⚠️ Potential issue | 🟠 MajorThe new by-name status helper can't report health correctly for non-default ports.
nimStatusByName()is now the generic status entry point, but its health probe is still hardcoded tolocalhost:8000. Any container started on another host port will be reported unhealthy, and another service on 8000 can produce a false healthy. This helper needs the mapped port as input or should read it fromdocker inspect.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/lib/nim.js` around lines 191 - 205, nimStatusByName currently probes health against a hardcoded localhost:8000; change it to derive the actual mapped host port for the container and probe that address instead. In the running branch of nimStatusByName, use docker inspect (e.g. read .NetworkSettings.Ports or use `docker port` for the container name) to obtain the host port mapped for container port 8000, build the host URL (host:mappedPort) and then curl that URL (/v1/models) to determine health; update the health check logic (the `health` and `healthy` calculation) to use the resolved host:port instead of localhost:8000. Ensure this handles missing mappings gracefully (fall back to unhealthy) and keeps ignoreError behavior.nemoclaw-blueprint/policies/openclaw-sandbox.yaml (1)
160-175:⚠️ Potential issue | 🟠 Major
nodeis still too broad for a “notifications only” egress path.Allowing
/usr/local/bin/nodemeans arbitrary JavaScript run by the agent can hit Telegram/Discord whenever notification credentials are present, so this is broader than the comment suggests. If the goal is to reserve these endpoints for OpenClaw notifications, scope the allow-list to a dedicated notifier launcher/helper instead of the general-purpose interpreter.Also applies to: 203-204
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nemoclaw-blueprint/policies/openclaw-sandbox.yaml` around lines 160 - 175, The policy currently allows the generic Node.js interpreter under the telegram egress rule ("telegram" block, "binaries" entry with path /usr/local/bin/node), which is too broad; replace that generic interpreter with a dedicated notifier launcher/helper binary (e.g., the OpenClaw notifier binary or wrapper) and update the "telegram" block's "binaries" entry to point to that specific executable; ensure any other egress blocks that reference /usr/local/bin/node are similarly scoped to the dedicated notifier (remove /usr/local/bin/node from "telegram" and the other referenced blocks and add the specific notifier binary path instead), and confirm the launcher is the only component allowed to call the telegram endpoints.test/onboard-selection.test.js (1)
83-91:⚠️ Potential issue | 🟠 MajorThe spawned test processes inherit host credentials.
Each
spawnSync(..., { env: { ...process.env, ... } })pulls in whatever provider keys are set on the runner. SincesetupNimbranches on env credentials, a developer/CI machine withNVIDIA_API_KEY,OPENAI_API_KEY, etc. can change the provider list and prompt order, making these assertions non-hermetic. The same...process.envmerge is repeated in the later cases too.🔒 Make the child env hermetic
- env: { - ...process.env, - HOME: tmpDir, - PATH: `${fakeBin}:${process.env.PATH || ""}`, - }, + env: { + HOME: tmpDir, + PATH: `${fakeBin}:${process.env.PATH || ""}`, + NVIDIA_API_KEY: "", + OPENAI_API_KEY: "", + ANTHROPIC_API_KEY: "", + GEMINI_API_KEY: "", + COMPATIBLE_API_KEY: "", + COMPATIBLE_ANTHROPIC_API_KEY: "", + },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/onboard-selection.test.js` around lines 83 - 91, The test spawns child Node processes using spawnSync and merges ...process.env, which leaks host credentials (e.g., OPENAI_API_KEY) and makes the test non-hermetic; update the spawnSync env objects (the block that currently uses env: { ...process.env, HOME: tmpDir, PATH: `${fakeBin}:${process.env.PATH || ""}` }) to construct a minimal, explicit environment instead of spreading process.env — e.g., set only HOME: tmpDir, PATH: `${fakeBin}:${process.env.PATH || ""}` (or PATH from process.env.PATH), and any other minimal runtime keys you actually rely on (NODE_ENV, CI) — and apply the same change to the other spawnSync cases so no provider keys are inherited.bin/lib/onboard.js (2)
1373-1385:⚠️ Potential issue | 🟠 MajorKeeping an existing sandbox skips the dashboard forward restart.
preflight()tears down the old18789forward, but this early return bypasses theforward startblock below. Re-running onboard and choosing to keep the existing sandbox leaves the dashboard unreachable even though the flow reports success.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/lib/onboard.js` around lines 1373 - 1385, The early return when the user chooses to keep the existing sandbox bypasses the subsequent forward-start logic that re-establishes the dashboard port torn down by preflight(), so ensure the forward is started before returning: after the "Keeping existing sandbox." branch (where sandboxName is returned), invoke the same forward-start sequence used below (e.g., call the forward.start()/startForward() routine or the function that handles the "forward start" block) so port 18789 is re-established for the dashboard; keep sandboxName returned as before but only after running the forward-start logic (reference symbols: preflight(), forward.start()/startForward(), sandboxName).
1774-1779:⚠️ Potential issue | 🟠 MajorThe local NIM fallback returns an unusable cloud config.
When
nim.waitForNimHealth()fails, this branch logs “Falling back to cloud API” but exits the selection loop withmodel = nulland without selecting or validating a real cloud model. The later inference setup then runs with an inconsistent provider/model pair.Also applies to: 1789-1794
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/lib/onboard.js` around lines 1774 - 1779, When nim.waitForNimHealth() fails the code sets model = null and nimContainer = null but does not select or validate a cloud provider/model, leaving an inconsistent provider/model state; change the failure branch so it clears nimContainer, forces selection of a cloud provider and model (e.g., call the existing cloud model selection routine or set provider = 'cloud' and pick a default from cloudModels), then validate that provider/model pair with the same validation function used elsewhere (e.g., validateProviderModel or selectModelForProvider) and only exit the selection loop when a valid cloud model is chosen; if validation fails continue the selection loop or abort with a clear error.
🟡 Minor comments (7)
test/e2e/e2e-cloud-experimental/features/skill/add-sandbox-skill.sh-19-20 (1)
19-20:⚠️ Potential issue | 🟡 MinorFix the fixture path in this usage comment.
The script's actual default is
features/skill/fixtures/skill-smoke-template.SKILL.mdon Line 38, so the path shown here points readers to the wrong file when they update the smoke template.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/e2e/e2e-cloud-experimental/features/skill/add-sandbox-skill.sh` around lines 19 - 20, Update the usage comment at the top of add-sandbox-skill.sh that mentions the default template: the comment currently points to the wrong fixture; change the path text to the actual default template path used by the script (features/skill/fixtures/skill-smoke-template.SKILL.md) so SKILL_FILE / SKILL_BODY documentation matches the behavior in the script.test/policies.test.js-90-96 (1)
90-96:⚠️ Potential issue | 🟡 MinorRestore the previous env var value instead of always deleting it.
At Line 95, unconditional
deletecan leak state across tests whenNEMOCLAW_OPENSHELL_BINwas pre-set by the runner/environment.Suggested fix
it("uses the resolved openshell binary when provided by the installer path", () => { + const previousOpenshellBin = process.env.NEMOCLAW_OPENSHELL_BIN; process.env.NEMOCLAW_OPENSHELL_BIN = "/tmp/fake path/openshell"; try { const cmd = policies.buildPolicySetCommand("/tmp/policy.yaml", "my-assistant"); assert.equal(cmd, "'/tmp/fake path/openshell' policy set --policy '/tmp/policy.yaml' --wait 'my-assistant'"); } finally { - delete process.env.NEMOCLAW_OPENSHELL_BIN; + if (previousOpenshellBin === undefined) { + delete process.env.NEMOCLAW_OPENSHELL_BIN; + } else { + process.env.NEMOCLAW_OPENSHELL_BIN = previousOpenshellBin; + } } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/policies.test.js` around lines 90 - 96, The test mutates process.env.NEMOCLAW_OPENSHELL_BIN and currently unconditionally deletes it in the finally block, which can leak state; change the test to save the previous value (e.g., const prev = process.env.NEMOCLAW_OPENSHELL_BIN) before assigning "/tmp/fake path/openshell" and in the finally block restore it: if prev is undefined delete process.env.NEMOCLAW_OPENSHELL_BIN else set process.env.NEMOCLAW_OPENSHELL_BIN = prev; keep the assertion using policies.buildPolicySetCommand unchanged..agents/skills/nemoclaw-reference/references/architecture.md-44-52 (1)
44-52:⚠️ Potential issue | 🟡 MinorReconcile the runtime model in this doc.
This section says the blueprint runtime lives in
nemoclaw/src/blueprint/, but the unchanged intro still describes a Python blueprint subprocess, and the lifecycle section below still omits the newly listedrollbackphase. Please update those sections together so the architecture reads consistently.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/skills/nemoclaw-reference/references/architecture.md around lines 44 - 52, Update the architecture doc to consistently reflect the TypeScript blueprint runtime under nemoclaw/src/blueprint/: remove or replace the outdated "Python blueprint subprocess" wording in the intro with a clear statement that the blueprint runtime is implemented in TypeScript in the plugin source tree (referencing runner.ts, ssrf.ts, snapshot.ts, state.ts), and amend the lifecycle section to include the missing "rollback" phase and note that runner.ts implements plan/apply/status/rollback behavior; keep descriptions aligned (e.g., mention snapshot.ts handles migration snapshot/restore and state.ts handles persistent run state) so all mentions match the new runtime model.test/security-binaries-restriction.test.js-23-29 (1)
23-29:⚠️ Potential issue | 🟡 MinorIgnore top-level comments when leaving
network_policies.This exit condition stops scanning on any column-0 text, so a
# ...comment between entries can end the section early and let later policy blocks skip thebinariesassertion.Suggested fix
- if (inNetworkPolicies && /^\S/.test(line) && line.trim() !== "") { + if (inNetworkPolicies && /^\S/.test(line) && line.trim() !== "" && !/^\s*#/.test(line)) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/security-binaries-restriction.test.js` around lines 23 - 29, The exit condition for leaving the network_policies block incorrectly treats any column-0 text (including top-level comments) as the end, so update the check in the block that uses inNetworkPolicies, currentBlock and the /^\S/ test (the if with inNetworkPolicies && /^\S/.test(line) && line.trim() !== "") to ignore top-level comments: first compute trimmedLine = line.trim(), and only treat a column-0 line as the section end if trimmedLine is non-empty and does NOT start with '#' (i.e. skip lines where trimmedLine.startsWith('#')), leaving comment lines inside network_policies and preventing premature exit so later policy blocks still get validated for binaries.test/e2e/e2e-cloud-experimental/test-port8080-conflict.sh-25-26 (1)
25-26:⚠️ Potential issue | 🟡 MinorFix the usage example path.
The documented command drops the
e2e-cloud-experimental/directory, so a copy/paste run from the repo root will miss this script.Suggested fix
-# NEMOCLAW_NON_INTERACTIVE=1 NVIDIA_API_KEY=nvapi-... bash test/e2e/test-port8080-conflict.sh +# NEMOCLAW_NON_INTERACTIVE=1 NVIDIA_API_KEY=nvapi-... bash test/e2e/e2e-cloud-experimental/test-port8080-conflict.sh🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/e2e/e2e-cloud-experimental/test-port8080-conflict.sh` around lines 25 - 26, Update the usage comment at the top of test-port8080-conflict.sh so the example command includes the correct relative path to this script (e2e-cloud-experimental/test-port8080-conflict.sh) instead of just test/e2e/test-port8080-conflict.sh; edit the comment lines near the shebang/usage block to show: NEMOCLAW_NON_INTERACTIVE=1 NVIDIA_API_KEY=nvapi-... bash e2e-cloud-experimental/test-port8080-conflict.sh so copying from the repo root runs the correct script.test/e2e/e2e-cloud-experimental/test-inference-local-chat.sh-20-21 (1)
20-21:⚠️ Potential issue | 🟡 MinorThe usage example points at the wrong script name.
The file is
test/e2e/e2e-cloud-experimental/test-inference-local-chat.sh, but the comment still tells users to rundemo-inference-local-chat.sh.Suggested fix
-# bash test/e2e/demo-inference-local-chat.sh +# bash test/e2e/e2e-cloud-experimental/test-inference-local-chat.sh🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/e2e/e2e-cloud-experimental/test-inference-local-chat.sh` around lines 20 - 21, Update the usage comment that currently references "demo-inference-local-chat.sh" to the correct script name "test-inference-local-chat.sh" so the example reflects the actual file; locate the top-of-file usage block containing the string "bash demo-inference-local-chat.sh" and replace it with "bash test-inference-local-chat.sh".test/e2e-gateway-isolation.sh-37-48 (1)
37-48:⚠️ Potential issue | 🟡 MinorDon't reuse the default image tag for local runs.
When
NEMOCLAW_TEST_IMAGEis unset, this still skips the build ifnemoclaw-isolation-testalready exists locally. A failed or interrupted previous run can then make this script validate an old image instead of the current checkout.♻️ Suggested guard
-# Skip build if image already exists (e.g., loaded from CI artifact) -if docker image inspect "$IMAGE" >/dev/null 2>&1; then +# Only reuse a preloaded image when the caller explicitly provided one. +if [ -n "${NEMOCLAW_TEST_IMAGE:-}" ] && docker image inspect "$IMAGE" >/dev/null 2>&1; then info "Using pre-built image: $IMAGE" else info "Building sandbox image..."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/e2e-gateway-isolation.sh` around lines 37 - 48, The script currently reuses a potentially stale local image when NEMOCLAW_TEST_IMAGE is unset because IMAGE defaults to a fixed tag; change the logic so that when NEMOCLAW_TEST_IMAGE is empty you generate a unique tag (e.g., include git commit SHA or a timestamp/short UUID) and assign it to IMAGE before the docker image inspect check, or alternatively skip the inspect check and always run the docker build when NEMOCLAW_TEST_IMAGE is unset; update the code paths around the IMAGE variable and the docker build block so the build is forced for local runs unless an explicit NEMOCLAW_TEST_IMAGE is provided.
🧹 Nitpick comments (16)
.github/workflows/docs-preview-deploy.yaml (1)
100-106: Optional: gate sticky-comment deletion to same-repo PRs.This avoids unnecessary API calls on fork PR closures, since preview comments are only created for same-repo PRs.
Suggested diff
- - name: Remove preview comment - if: steps.meta.outputs.action == 'closed' + - name: Remove preview comment + if: steps.meta.outputs.action == 'closed' && steps.meta.outputs.same-repo == 'true' uses: marocchino/sticky-pull-request-comment@v2 with: header: pr-preview number: ${{ steps.meta.outputs.pr-number }} delete: true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/docs-preview-deploy.yaml around lines 100 - 106, Update the "Remove preview comment" step so it only runs for closed PRs that originate from the same repository; change the step's if condition (currently if: steps.meta.outputs.action == 'closed') to also verify the PR is not from a fork by comparing the PR head repo to the workflow repo (for example: ensure github.event.pull_request.head.repo.full_name == github.repository in the if expression) so the marocchino/sticky-pull-request-comment deletion only executes for same-repo PRs.scripts/docs-to-skills.py (1)
1350-1373: Fragile path matching using string containment.The check
if ".agents/skills" in str(out_dir)can match unintended paths like.agents/skills-backup/,.agents/skills2/, or subdirectories like.agents/skills/foo/. Consider using a more precise check.♻️ Proposed fix for precise path matching
# Only create symlink if output is under .agents/skills - if ".agents/skills" in str(out_dir): - agents_skills = Path(out_dir) + agents_skills = Path(".agents/skills") + try: + # Check if out_dir is exactly .agents/skills or a child of it + out_dir.resolve().relative_to(agents_skills.resolve()) + is_agents_skills_output = True + except ValueError: + is_agents_skills_output = out_dir.resolve() == agents_skills.resolve() + if is_agents_skills_output:Alternatively, for an exact match only:
if out_dir.resolve() == Path(".agents/skills").resolve():🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/docs-to-skills.py` around lines 1350 - 1373, The current fragile check uses string containment ("if '.agents/skills' in str(out_dir)") which can match unintended paths; update the logic around args.output_dirs and out_dir to perform a precise Path comparison instead (e.g., compare out_dir.resolve() to Path(".agents/skills").resolve() or test out_dir.samefile(Path(".agents/skills"))), locating the check in the loop that iterates args.output_dirs and references claude_skills, agents_skills, and rel; replace the substring match with the exact Path equality/samefile check so only the intended .agents/skills directory triggers symlink creation.docs/_ext/search_assets/modules/SearchEngine.js (1)
235-237: Consider logging skipped documents during index build.At Line 235, swallowing per-document indexing failures without any trace makes data-quality regressions hard to detect.
Proposed refactor
- } catch (_docError) { - // Skip documents that fail to index + } catch (docError) { + console.warn('Skipping document during index build', { + docId: doc?.id, + error: docError + }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/_ext/search_assets/modules/SearchEngine.js` around lines 235 - 237, The catch block currently swallows per-document indexing errors (catch (_docError)) so failures are invisible; update that catch to log the error and document identifier/metadata (e.g., doc.id or the document object) using the module's logger if available (this.logger or similar) or console.error, including a clear message and _docError details, then continue to skip the document as before.nemoclaw/src/onboard/config.ts (2)
72-85: Consider improving error handling in the fallback path.The fallback logic in
ensureConfigDircatches all errors silently (line 77) and the fallback creation (lines 79-80) can also throw without being caught. If both paths fail, the error from the fallback will propagate with no context about the original failure.♻️ Suggested improvement
function ensureConfigDir(): void { if (configDirCreated) return; if (!existsSync(configDir)) { try { mkdirSync(configDir, { recursive: true }); - } catch { + } catch (primaryError) { configDir = join(tmpdir(), ".nemoclaw"); if (!existsSync(configDir)) { - mkdirSync(configDir, { recursive: true }); + try { + mkdirSync(configDir, { recursive: true }); + } catch (fallbackError) { + throw new Error( + `Failed to create config directory at primary (${process.env.HOME}/.nemoclaw) and fallback (${configDir}): ${String(fallbackError)}` + ); + } } } } configDirCreated = true; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nemoclaw/src/onboard/config.ts` around lines 72 - 85, The ensureConfigDir function currently swallows the original error in the first mkdirSync attempt and does not guard the fallback mkdirSync, so failures lose context; update ensureConfigDir to catch the initial error into a variable (e.g., err1) when the first mkdirSync fails, then attempt the fallback (set configDir = join(tmpdir(), ".nemoclaw")) inside its own try/catch so you can capture a second error (e.g., err2) if that also fails, and finally throw a new Error that includes both err1 and err2 messages (or attach them as cause) to preserve full context; reference ensureConfigDir, configDir, configDirCreated, mkdirSync, existsSync, tmpdir, and join when making the changes.
8-8: Module-level mutable state may cause test isolation issues.The
let configDiris mutable and modified byensureConfigDir(). Once the fallback path is used, subsequent calls in the same process will use the modified path. This could cause unexpected behavior in tests or long-running processes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@nemoclaw/src/onboard/config.ts` at line 8, The module-level mutable variable configDir causes shared-state leaks; replace it with a pure accessor and stop mutating module state: remove the let configDir and add a function getConfigDir() that returns join(process.env.HOME ?? tmpdir(), ".nemoclaw"), update ensureConfigDir() to call getConfigDir() (or accept an optional path param) and remove any code that reassigns configDir, and update all other references to use getConfigDir() so tests and long-running processes no longer rely on mutated module state.package.json (1)
14-14: Consider extracting the complex prepare script to a separate shell script.The inline prepare script handles multiple conditional paths (git presence, prek binary, prek package). While functional, extracting this to a dedicated
scripts/prepare.shwould improve readability and maintainability.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 14, The inline "prepare" entry in package.json is doing multiple conditional checks and should be moved to a dedicated script for clarity and maintainability: create a new executable scripts/prepare.sh that contains the current conditional logic (checking for .git, testing for the prek binary, and handling the presence of node_modules/@j178/prek), make it executable, and then replace the package.json "prepare" value with a simple invocation of that script (e.g., calling scripts/prepare.sh). Ensure the new script preserves identical behavior and exit codes and reference the same symbols (prek, node_modules/@j178/prek) used in the original prepare logic.test/e2e/e2e-cloud-experimental/skip/04-nemoclaw-openshell-status-parity.sh (1)
52-65: Consider simplifying the Ready check condition.Line 61 checks
cols.includes("Ready") && !cols.includes("NotReady"). If columns include "Ready" as a status, the!cols.includes("NotReady")check may be unnecessary unless a row can contain both states simultaneously. This is minor and the current implementation is safe.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/e2e/e2e-cloud-experimental/skip/04-nemoclaw-openshell-status-parity.sh` around lines 52 - 65, The Ready-check inside the Node one-liner can be simplified by removing the redundant "!cols.includes('NotReady')" check; update the predicate that sets ok (the arrow function used in clean.split(...).some(...)) to only verify cols.includes("Ready") (or a word-boundary check like matching /\bReady\b/ against the trimmed line) so the readiness determination uses a single clear condition; modify the lambda that assigns ok accordingly.test/credentials.test.js (1)
33-42: Prefer behavior assertions over source-regex assertions.At Line 39–Line 41, this test is tightly coupled to code formatting/order and can fail on harmless refactors. Consider exercising the prompt error path and asserting observable behavior (rejection + SIGINT intent) instead of matching source text.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/credentials.test.js` around lines 33 - 42, The test currently inspects credentials.js source via regex (matching "return new Promise((resolve, reject) => {" and 'reject(err); process.kill(process.pid, "SIGINT");'), which is fragile; instead, mock/stub the secret prompt used by the credentials.js module so it throws an error, spy on process.kill, import and invoke the exported function that triggers the secret prompt, then assert the returned promise rejects and that process.kill was called with process.pid and "SIGINT" (replace the source-regex assertions with these behavioral assertions).scripts/check-spdx-headers.sh (2)
38-46: Consider adding.jsxto the//comment style pattern.The pattern handles
.tsxbut omits.jsx, which should also use//style comments.♻️ Suggested fix
case "$base" in Dockerfile | *.dockerfile | *.Dockerfile) echo "#" ;; - *.ts | *.tsx | *.js | *.mjs | *.cjs) echo "//" ;; + *.ts | *.tsx | *.js | *.jsx | *.mjs | *.cjs) echo "//" ;; *) echo "#" ;; esac🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/check-spdx-headers.sh` around lines 38 - 46, The case in function comment_style_for currently maps *.ts, *.tsx, *.js, *.mjs, *.cjs to "//" but omits JSX files; update the pattern in comment_style_for to include *.jsx alongside *.tsx so files like *.jsx also return "//". Locate the case arm inside comment_style_for and add "*.jsx" to the list of alternatives that echo "//" (keeping the existing order and spacing).
61-85: Minor: temp file not cleaned up on partial failure ininsert_spdx.If
chmodormvfails on Line 84, the temp file in$tmpis left behind. Consider adding cleanup logic or a trap within the function.♻️ Suggested improvement
insert_spdx() { local file=$1 local style style=$(comment_style_for "$file") local tmp local mode tmp="$(mktemp "${TMPDIR:-/tmp}/nemoclaw-spdx.XXXXXX")" + trap 'rm -f "$tmp"' RETURN mode="$(stat -c '%a' "$file" 2>/dev/null || stat -f '%Lp' "$file")"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/check-spdx-headers.sh` around lines 61 - 85, The insert_spdx function can leave the temporary file ($tmp) behind if chmod or mv fails; update insert_spdx to create a cleanup trap or explicit error-path removal that deletes "$tmp" on any failure and disables the trap after successful mv, ensuring we still preserve the original file mode (mode variable) and that the trap references the same tmp variable; target the tmp creation/cleanup around the mktemp, chmod and mv sequence inside insert_spdx and ensure the trap is cleaned up on success.test/validate-blueprint.test.ts (2)
22-28: Consider adding error handling for blueprint parsing.Parsing
blueprint.yamlat module scope means any parsing errors will crash the entire test suite rather than reporting as individual test failures. Consider wrapping the parsing in a try-catch or moving it inside abeforeAllhook.♻️ Suggested improvement
-const bp = YAML.parse(readFileSync(BLUEPRINT_PATH, "utf-8")) as Record<string, unknown>; -const declared = Array.isArray(bp?.profiles) ? (bp.profiles as string[]) : []; -const defined = - (bp?.components as Record<string, unknown> | undefined)?.inference != null - ? ((bp.components as Record<string, unknown>).inference as Record<string, unknown>).profiles as - Record<string, Record<string, unknown>> | undefined - : undefined; +let bp: Record<string, unknown>; +let declared: string[]; +let defined: Record<string, Record<string, unknown>> | undefined; + +beforeAll(() => { + bp = YAML.parse(readFileSync(BLUEPRINT_PATH, "utf-8")) as Record<string, unknown>; + declared = Array.isArray(bp?.profiles) ? (bp.profiles as string[]) : []; + defined = + (bp?.components as Record<string, unknown> | undefined)?.inference != null + ? ((bp.components as Record<string, unknown>).inference as Record<string, unknown>).profiles as + Record<string, Record<string, unknown>> | undefined + : undefined; +});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/validate-blueprint.test.ts` around lines 22 - 28, Parsing blueprint.yaml at module scope (YAML.parse(readFileSync(BLUEPRINT_PATH, "utf-8")) producing bp and derived declared/defined) can crash the whole test run on parse errors; move the file read and YAML.parse into a beforeAll hook or wrap them in a try-catch inside beforeAll so you can surface parsing errors as test failures (use BLUEPRINT_PATH, readFileSync, YAML.parse, bp, declared, defined) and call fail() or throw a controlled error from the hook to report the failure without aborting the test runner.
51-60: Clarify endpoint validation logic fordynamic_endpointprofiles.When
dynamic_endpoint === true, the test only verifies theendpointkey exists (Line 56) but doesn't check its value. When false, it requires a truthy value (Line 58). This asymmetry is intentional per the summary but could benefit from a brief comment explaining why dynamic endpoints allow empty/placeholder values.📝 Suggested clarification
for (const field of REQUIRED_PROFILE_FIELDS) { it(`has non-empty '${field}'`, () => { const cfg = defined?.[name]; if (!cfg) return; // covered by "has a definition" + // dynamic_endpoint profiles may have placeholder endpoint values resolved at runtime if (field === "endpoint" && cfg.dynamic_endpoint === true) { expect(field in cfg).toBe(true); } else { expect(cfg[field]).toBeTruthy(); } }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/validate-blueprint.test.ts` around lines 51 - 60, The test in the loop over REQUIRED_PROFILE_FIELDS treats "endpoint" specially when cfg.dynamic_endpoint === true by only asserting the key exists rather than asserting a truthy value; add a short explanatory comment inside that branch (near the conditional checking dynamic_endpoint and the expect(field in cfg) line) clarifying that profiles with dynamic_endpoint may provide a placeholder/empty endpoint at config time and are validated dynamically at runtime, so the spec only requires the key to be present and not a truthy value; reference REQUIRED_PROFILE_FIELDS, cfg.dynamic_endpoint, and the endpoint check in the test to locate where to add the comment.docs/reference/inference-profiles.md (2)
56-64: Colons used as general punctuation between clauses.Per the style guide, colons should only introduce a list. Lines 56, 58, 60, and 62 use colons to separate a label from its explanation. Consider restructuring these items.
Suggested restructure
-- OpenAI-compatible providers: - NemoClaw tries `/responses` first, then `/chat/completions`. +- **OpenAI-compatible providers** — NemoClaw tries `/responses` first, then `/chat/completions`.Or use a definition list / nested structure where the provider type is a sub-heading.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/reference/inference-profiles.md` around lines 56 - 64, The documentation uses colons incorrectly as general punctuation in the list items describing provider behaviors; update the sentences for the OpenAI/Anthropic/NVIDIA/Compatible endpoint lines so the provider labels are not followed by colons (e.g., rephrase "OpenAI-compatible providers: NemoClaw tries `/responses` first..." to "For OpenAI-compatible providers, NemoClaw tries `/responses` first, then `/chat/completions`") or convert the list into a definition/nested structure with provider types as sub-headings (refer to the terms "OpenAI-compatible providers", "Anthropic-compatible providers", "NVIDIA Endpoints manual model entry", and "Compatible endpoint flows") so each label introduces the explanation without using a colon as general punctuation.
86-88: Missing "Next Steps" section.Per page structure guidelines, documentation pages should include a "Next Steps" section at the bottom linking to related pages. The current ending abruptly references another document without providing additional navigation options.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/reference/inference-profiles.md` around lines 86 - 88, Add a "Next Steps" section after the "Runtime Switching" paragraph that provides clear navigation to related docs; include the existing "Switch Inference Models" link (link text "Switch Inference Models") plus 1–2 other relevant pages such as "Inference Providers" and "Model Selection" (or similar) to guide readers, using the same relative-link style as the current reference to ensure consistent formatting and placement at the bottom of the page.test/onboard.test.js (1)
427-436: Consider usingfindLastfor cleaner JSON payload extraction.The
.slice().reverse().find()pattern works butfindLast(available in Node 18+) is more idiomatic.Suggested simplification
- const payloadLine = result.stdout - .trim() - .split("\n") - .slice() - .reverse() - .find((line) => line.startsWith("{") && line.endsWith("}")); + const payloadLine = result.stdout + .trim() + .split("\n") + .findLast((line) => line.startsWith("{") && line.endsWith("}"));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/onboard.test.js` around lines 427 - 436, Replace the verbose reverse-find pattern used to extract the JSON payload from stdout (the payloadLine computation that uses result.stdout.trim().split("\n").slice().reverse().find(...)) with the more idiomatic Node 18+ Array.prototype.findLast call to locate the last line that starts with "{" and ends with "}", keeping the subsequent JSON.parse(payloadLine) and assertions (payload, payload.liveExists, payload.sandbox) unchanged..agents/skills/nemoclaw-reference/references/inference-profiles.md (1)
51-53: Consider varying the list item phrasing.Static analysis flagged three successive items beginning with "Local". While not incorrect, varying the structure improves readability.
Suggested alternative
-- Local Ollama -- Local NVIDIA NIM -- Local vLLM +- Ollama (local) +- NVIDIA NIM (local) +- vLLM (local)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/skills/nemoclaw-reference/references/inference-profiles.md around lines 51 - 53, The three list items ("Local Ollama", "Local NVIDIA NIM", "Local vLLM") use the same leading word which hurts readability; change the phrasing to vary structure (for example: "Ollama (local)", "NVIDIA NIM — local deployment", "vLLM (local runtime)") or similar alternations so each item reads distinctively while preserving meaning; update the items where these exact strings appear.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ec12dd33-e5be-4c15-b2fc-e5c4254bf7c0
⛔ Files ignored due to path filters (3)
nemoclaw-blueprint/uv.lockis excluded by!**/*.locknemoclaw/package-lock.jsonis excluded by!**/package-lock.jsonpackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (132)
.agents/skills/docs/nemoclaw-configure-inference/SKILL.md.agents/skills/docs/nemoclaw-reference/references/inference-profiles.md.agents/skills/nemoclaw-configure-inference/SKILL.md.agents/skills/nemoclaw-deploy-remote/SKILL.md.agents/skills/nemoclaw-get-started/SKILL.md.agents/skills/nemoclaw-manage-policy/SKILL.md.agents/skills/nemoclaw-monitor-sandbox/SKILL.md.agents/skills/nemoclaw-overview/SKILL.md.agents/skills/nemoclaw-overview/references/how-it-works.md.agents/skills/nemoclaw-overview/references/overview.md.agents/skills/nemoclaw-overview/references/release-notes.md.agents/skills/nemoclaw-reference/SKILL.md.agents/skills/nemoclaw-reference/references/architecture.md.agents/skills/nemoclaw-reference/references/commands.md.agents/skills/nemoclaw-reference/references/inference-profiles.md.agents/skills/nemoclaw-reference/references/network-policies.md.agents/skills/nemoclaw-reference/references/troubleshooting.md.agents/skills/nemoclaw-workspace/SKILL.md.agents/skills/nemoclaw-workspace/references/workspace-files.md.claude/skills.dockerignore.github/PULL_REQUEST_TEMPLATE.md.github/workflows/docs-preview-deploy.yaml.github/workflows/e2e-brev.yaml.github/workflows/nightly-e2e.yaml.github/workflows/pr.yaml.gitignore.pre-commit-config.yamlDockerfileMakefileREADME.mdbin/lib/credentials.jsbin/lib/inference-config.jsbin/lib/local-inference.jsbin/lib/nim.jsbin/lib/onboard.jsbin/lib/policies.jsbin/lib/preflight.jsbin/lib/registry.jsbin/lib/resolve-openshell.jsbin/nemoclaw.jsci/coverage-threshold.jsoncommitlint.config.jsdocs/_ext/search_assets/main.jsdocs/_ext/search_assets/modules/ResultRenderer.jsdocs/_ext/search_assets/modules/SearchEngine.jsdocs/_ext/search_assets/modules/SearchPageManager.jsdocs/about/how-it-works.mddocs/inference/switch-inference-providers.mddocs/reference/architecture.mddocs/reference/commands.mddocs/reference/inference-profiles.mddocs/reference/troubleshooting.mdeslint.config.mjsinstall.shjsconfig.jsonnemoclaw-blueprint/Makefilenemoclaw-blueprint/migrations/snapshot.pynemoclaw-blueprint/orchestrator/__init__.pynemoclaw-blueprint/orchestrator/runner.pynemoclaw-blueprint/orchestrator/test_endpoint_validation.pynemoclaw-blueprint/policies/openclaw-sandbox.yamlnemoclaw-blueprint/policies/presets/discord.yamlnemoclaw-blueprint/policies/presets/docker.yamlnemoclaw-blueprint/policies/presets/huggingface.yamlnemoclaw-blueprint/policies/presets/jira.yamlnemoclaw-blueprint/policies/presets/outlook.yamlnemoclaw-blueprint/policies/presets/slack.yamlnemoclaw-blueprint/policies/presets/telegram.yamlnemoclaw-blueprint/pyproject.tomlnemoclaw/eslint.config.mjsnemoclaw/package.jsonnemoclaw/src/blueprint/runner.test.tsnemoclaw/src/blueprint/runner.tsnemoclaw/src/blueprint/snapshot.test.tsnemoclaw/src/blueprint/snapshot.tsnemoclaw/src/blueprint/ssrf.test.tsnemoclaw/src/blueprint/ssrf.tsnemoclaw/src/blueprint/state.tsnemoclaw/src/commands/migration-state.test.tsnemoclaw/src/index.tsnemoclaw/src/onboard/config.test.tsnemoclaw/src/onboard/config.tspackage.jsonscripts/backup-workspace.shscripts/check-spdx-headers.shscripts/docs-to-skills.pyscripts/nemoclaw-start.shscripts/setup-spark.shscripts/setup.shscripts/telegram-bridge.jsscripts/walkthrough.shscripts/write-auth-profile.pyscripts/write-auth-profile.tsspark-install.mdtest/Dockerfile.sandboxtest/credential-exposure.test.jstest/credentials.test.jstest/e2e-gateway-isolation.shtest/e2e-test.shtest/e2e/brev-e2e.test.jstest/e2e/e2e-cloud-experimental/check-docs.shtest/e2e/e2e-cloud-experimental/checks/02-inference-local-http.shtest/e2e/e2e-cloud-experimental/checks/03-security-checks.shtest/e2e/e2e-cloud-experimental/expect-interactive-install.shtest/e2e/e2e-cloud-experimental/features/skill/add-sandbox-skill.shtest/e2e/e2e-cloud-experimental/features/skill/fixtures/skill-smoke-template.SKILL.mdtest/e2e/e2e-cloud-experimental/features/skill/lib/README.mdtest/e2e/e2e-cloud-experimental/features/skill/lib/validate_repo_skills.shtest/e2e/e2e-cloud-experimental/features/skill/lib/validate_sandbox_openclaw_skills.shtest/e2e/e2e-cloud-experimental/features/skill/verify-sandbox-skill-via-agent.shtest/e2e/e2e-cloud-experimental/openclaw-tui-in-sandbox.shtest/e2e/e2e-cloud-experimental/skip/01-onboard-completion.shtest/e2e/e2e-cloud-experimental/skip/04-nemoclaw-openshell-status-parity.shtest/e2e/e2e-cloud-experimental/skip/05-network-policy.shtest/e2e/e2e-cloud-experimental/skip/README.mdtest/e2e/e2e-cloud-experimental/test-inference-local-chat.shtest/e2e/e2e-cloud-experimental/test-port8080-conflict.shtest/e2e/test-double-onboard.shtest/e2e/test-e2e-cloud-experimental.shtest/e2e/test-full-e2e.shtest/inference-config.test.jstest/local-inference.test.jstest/nemoclaw-start.test.jstest/onboard-selection.test.jstest/onboard.test.jstest/policies.test.jstest/preflight.test.jstest/runner.test.jstest/security-binaries-restriction.test.jstest/validate-blueprint.test.tsvitest.config.ts
💤 Files with no reviewable changes (10)
- .dockerignore
- nemoclaw-blueprint/orchestrator/init.py
- nemoclaw-blueprint/pyproject.toml
- .agents/skills/docs/nemoclaw-configure-inference/SKILL.md
- .agents/skills/docs/nemoclaw-reference/references/inference-profiles.md
- scripts/write-auth-profile.py
- nemoclaw-blueprint/orchestrator/runner.py
- nemoclaw-blueprint/Makefile
- nemoclaw-blueprint/orchestrator/test_endpoint_validation.py
- nemoclaw-blueprint/migrations/snapshot.py
✅ Files skipped from review due to trivial changes (31)
- .agents/skills/nemoclaw-reference/references/troubleshooting.md
- commitlint.config.js
- .gitignore
- .agents/skills/nemoclaw-get-started/SKILL.md
- .claude/skills
- .github/PULL_REQUEST_TEMPLATE.md
- bin/lib/registry.js
- nemoclaw-blueprint/policies/presets/huggingface.yaml
- .agents/skills/nemoclaw-reference/SKILL.md
- bin/lib/resolve-openshell.js
- docs/reference/architecture.md
- docs/reference/troubleshooting.md
- .agents/skills/nemoclaw-reference/references/commands.md
- ci/coverage-threshold.json
- docs/_ext/search_assets/modules/ResultRenderer.js
- nemoclaw/src/index.ts
- docs/reference/commands.md
- scripts/setup-spark.sh
- scripts/telegram-bridge.js
- docs/_ext/search_assets/main.js
- .agents/skills/nemoclaw-workspace/references/workspace-files.md
- nemoclaw/src/commands/migration-state.test.ts
- docs/inference/switch-inference-providers.md
- jsconfig.json
- test/e2e/e2e-cloud-experimental/features/skill/lib/README.md
- test/e2e/e2e-cloud-experimental/skip/README.md
- .agents/skills/nemoclaw-overview/references/how-it-works.md
- test/e2e/e2e-cloud-experimental/features/skill/fixtures/skill-smoke-template.SKILL.md
- test/e2e/e2e-cloud-experimental/expect-interactive-install.sh
- .agents/skills/nemoclaw-configure-inference/SKILL.md
- .agents/skills/nemoclaw-workspace/SKILL.md
| // Only pass non-sensitive env vars to the sandbox. NVIDIA_API_KEY is NOT | ||
| // needed inside the sandbox — inference is proxied through the OpenShell | ||
| // gateway which injects the stored credential server-side. The gateway | ||
| // also strips any Authorization headers sent by the sandbox client. | ||
| // See: crates/openshell-sandbox/src/proxy.rs (header stripping), | ||
| // crates/openshell-router/src/backend.rs (server-side auth injection). | ||
| const envArgs = [formatEnvAssignment("CHAT_UI_URL", chatUiUrl)]; | ||
| const sandboxEnv = { ...process.env }; | ||
| delete sandboxEnv.NVIDIA_API_KEY; | ||
| const discordToken = getCredential("DISCORD_BOT_TOKEN") || process.env.DISCORD_BOT_TOKEN; |
There was a problem hiding this comment.
The sandbox env still includes the new remote-provider secrets.
sandboxEnv clones process.env and only deletes NVIDIA_API_KEY. After adding OpenAI, Anthropic, Gemini, and custom endpoint flows, those API keys will still be inherited by openshell sandbox create, which contradicts the “non-sensitive only” contract in this block.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@bin/lib/onboard.js` around lines 1415 - 1424, The sandboxEnv currently clones
process.env then only deletes NVIDIA_API_KEY, which still leaks remote-provider
secrets (OpenAI/Anthropic/Gemini/custom endpoints). Update the logic around
sandboxEnv (the object created near formatEnvAssignment and getCredential usage)
to remove or avoid copying these provider secrets: at minimum delete
OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY and any custom provider
keys/endpoints your app uses (e.g., REMOTE_PROVIDER_API_KEY or
REMOTE_PROVIDER_ENDPOINT) from sandboxEnv before passing to the sandbox;
alternatively replace the clone-with-deletes with a whitelist approach (build
sandboxEnv only from safe keys such as CHAT_UI_URL and explicit non-sensitive
vars). Ensure getCredential usage for DISCORD_BOT_TOKEN remains unchanged.
| preferredInferenceApi = await validateOpenAiLikeSelection( | ||
| "Local NVIDIA NIM", | ||
| endpointUrl, | ||
| model, | ||
| credentialEnv | ||
| ); |
There was a problem hiding this comment.
Don't send the user's OpenAI key to local NIM/vLLM probes.
These calls pass credentialEnv = "OPENAI_API_KEY" into validateOpenAiLikeSelection(), so a saved cloud key gets attached as a Bearer token when probing localhost. Local NIM/vLLM do not need that secret, and this leaks it into local service logs unnecessarily.
Also applies to: 1899-1904
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@bin/lib/onboard.js` around lines 1783 - 1788, The code is passing the cloud
credentialEnv ("OPENAI_API_KEY") into validateOpenAiLikeSelection for local
backends, which causes the user's OpenAI key to be sent to local probes; update
the calls that construct preferredInferenceApi for local options (e.g. the
invocation with "Local NVIDIA NIM" and the similar "Local vLLM" call) to pass no
credentialEnv (use null/undefined or an explicit empty string) so
validateOpenAiLikeSelection does not attach a Bearer token when probing
localhost, and ensure validateOpenAiLikeSelection still handles a missing
credentialEnv safely.
| const credentialEnv = inferenceCfg.credential_env; | ||
| const credentialDefault = inferenceCfg.credential_default ?? ""; | ||
| let credential = ""; | ||
| if (credentialEnv) { | ||
| credential = process.env[credentialEnv] ?? credentialDefault; | ||
| } | ||
|
|
||
| const providerArgs = [ | ||
| "openshell", | ||
| "provider", | ||
| "create", | ||
| "--name", | ||
| providerName, | ||
| "--type", | ||
| providerType, | ||
| ]; | ||
| // Pass the env-var NAME (not the value) to --credential; openshell reads the value from the env. | ||
| // Scope the credential to the subprocess to avoid leaking into later commands. | ||
| const credEnv: Record<string, string> = {}; | ||
| if (credential) { | ||
| credEnv.OPENAI_API_KEY = credential; | ||
| providerArgs.push("--credential", "OPENAI_API_KEY"); | ||
| } | ||
| if (endpoint) { | ||
| providerArgs.push("--config", `OPENAI_BASE_URL=${endpoint}`); | ||
| } |
There was a problem hiding this comment.
Map credentials and base-url config by provider_type.
actionApply() always rewrites the secret into OPENAI_API_KEY and always sends OPENAI_BASE_URL=.... That only matches OpenAI-style providers; Anthropic and any other provider types modeled in the blueprint will be configured with the wrong env/config keys.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@nemoclaw/src/blueprint/runner.ts` around lines 264 - 289, The code currently
hardcodes OPENAI_* env/config keys when setting credentials and base URL; change
this to map keys by providerType (use the existing providerType/inferenceCfg
variables). Create a small map or function (e.g., const providerKeyMap = {
openai: {cred: "OPENAI_API_KEY", base: "OPENAI_BASE_URL"}, anthropic: {cred:
"ANTHROPIC_API_KEY", base: "ANTHROPIC_API_URL"}, ... }) and use it inside the
credential/endpoint branch where providerArgs, credEnv, and --config are built:
set const credVar = providerKeyMap[providerType].cred (fallback to a safe
default), assign credEnv[credVar] = credential and push ("--credential",
credVar), and for endpoint push ("--config",
`${providerKeyMap[providerType].base}=${endpoint}`). Update any related use in
actionApply() to use the same mapping so non-OpenAI providers get the correct
env/config keys.
| await execa(providerArgs[0], providerArgs.slice(1), { | ||
| reject: false, | ||
| stdout: "pipe", | ||
| stderr: "pipe", | ||
| env: { ...process.env, ...credEnv }, | ||
| }); | ||
|
|
||
| progress(70, "Setting inference route"); | ||
| await runCmd(["openshell", "inference", "set", "--provider", providerName, "--model", model], { | ||
| reject: false, | ||
| }); |
There was a problem hiding this comment.
Treat provider setup and route selection failures as fatal.
Both subprocess results are discarded here. A failed provider create or inference set still falls through to PROGRESS:100 and writes plan.json as if apply succeeded, so reruns can silently keep stale routing.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@nemoclaw/src/blueprint/runner.ts` around lines 291 - 301, The subprocess
results from the execa call (using providerArgs) and the
runCmd(["openshell","inference","set",...]) call are currently ignored so
failures still allow progress to 100 and writing plan.json; capture their
results and treat non-zero exits as fatal: inspect the return (e.g.
result.exitCode or result.status) from the execa(...) invocation and from
runCmd(...) and if non-zero throw an Error (or process.exit(1)) including
stdout/stderr and context (providerName, model) to stop execution and avoid
calling progress(100) / writing plan.json; alternatively enable reject:true so
exceptions propagate and catch/log them with details before exiting.
| const blueprint = loadBlueprint(); | ||
|
|
||
| switch (action) { | ||
| case "plan": | ||
| await actionPlan(profile, blueprint, { dryRun, endpointUrl }); | ||
| break; | ||
| case "apply": | ||
| await actionApply(profile, blueprint, { planPath, endpointUrl }); | ||
| break; | ||
| case "status": | ||
| actionStatus(runId); | ||
| break; | ||
| case "rollback": | ||
| if (!runId) { | ||
| throw new Error("--run-id is required for rollback"); | ||
| } | ||
| await actionRollback(runId); | ||
| break; |
There was a problem hiding this comment.
Don't require blueprint.yaml for status and rollback.
main() loads the blueprint before dispatching any action, even though these two commands only use persisted run state. That makes routine inspection and rollback fail outside the original blueprint directory for no functional reason.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@nemoclaw/src/blueprint/runner.ts` around lines 428 - 445, Currently
loadBlueprint() is called unconditionally before the switch, causing status and
rollback to fail when blueprint.yaml is absent; change the flow so
loadBlueprint() is only invoked inside the cases that need it (call
loadBlueprint() inside the "plan" and "apply" branches before
actionPlan/actionApply) and remove the unconditional call so actionStatus(runId)
and actionRollback(runId) run without requiring a blueprint; ensure you keep the
existing checks (e.g., the --run-id check before actionRollback) and keep
function names exactly as in the diff (loadBlueprint, actionPlan, actionApply,
actionStatus, actionRollback).
| run_as_sandbox() { | ||
| docker run --rm --entrypoint "" "$IMAGE" gosu sandbox bash -c "$1" 2>&1 | ||
| } | ||
|
|
||
| # Helper: run a command inside the container as root | ||
| run_as_root() { | ||
| docker run --rm --entrypoint "" "$IMAGE" bash -c "$1" 2>&1 | ||
| } |
There was a problem hiding this comment.
These probes short-circuit the whole script under set -e.
OUT=$(run_as_root ...) / OUT=$(run_as_sandbox ...) inherits the inner exit code. For negative checks like missing users, missing gosu, missing iptables, or broken symlinks, Bash exits before fail() runs, so you lose the per-assertion summary.
🧪 Capture the exit status explicitly
-OUT=$(run_as_root "command -v gosu && gosu --version")
-if echo "$OUT" | grep -q "gosu"; then
+set +e
+OUT=$(run_as_root "command -v gosu && gosu --version")
+rc=$?
+set -e
+if [ "$rc" -eq 0 ] && echo "$OUT" | grep -q "gosu"; then
pass "gosu installed"
else
fail "gosu not found: $OUT"
fiAlso applies to: 63-65, 118-123, 139-143, 151-153, 165-169
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@test/e2e-gateway-isolation.sh` around lines 51 - 58, The test calls that run
container commands via run_as_root() and run_as_sandbox() must not let their
inner exit code trigger the script-wide set -e; wrap each invocation so you
capture both stdout and the exit status (e.g. temporarily disable errexit with
set +e before running, capture OUT="$(run_as_root ...)" and rc=$?, then restore
set -e and use rc to decide pass/fail) or immediately follow the command with
rc=$? before any other command; update every occurrence that does
OUT=$(run_as_root ...) or OUT=$(run_as_sandbox ...) (and similar checks for
missing gosu/iptables/symlinks) to use this pattern so fail() runs and
per-assertion summaries aren't skipped.
| function brev(...args) { | ||
| return execFileSync("brev", args, { | ||
| encoding: "utf-8", | ||
| timeout: 60_000, | ||
| stdio: ["pipe", "pipe", "pipe"], | ||
| }).trim(); | ||
| } |
There was a problem hiding this comment.
Isolate Brev state under a temp home for this suite.
This writes onboarding_step.json into the caller's real ~/.brev and then logs in against the same host config. A local run will mutate or overwrite an existing Brev session, and the suite never restores that state. Point the brev() helper and this file write at a temporary HOME/config directory and clean it up in afterAll.
Also applies to: 115-121
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@test/e2e/brev-e2e.test.js` around lines 39 - 45, The test helper brev()
currently invokes the real user HOME and causes onboarding_step.json and session
files to be written into ~/.brev; change the suite to create a temporary
directory for HOME (or BREV_CONFIG_HOME) at test setup, update brev(...) to pass
env with HOME set to that temp dir so execFileSync isolates config, update any
direct writes that reference onboarding_step.json to target the temp config dir
instead (also apply the same change to the code around lines 115-121 that write
session/config), and add an afterAll cleanup that removes the temp directory and
restores any modified env.
| execSync( | ||
| `rsync -az --delete --exclude node_modules --exclude .git --exclude dist --exclude .venv "${REPO_DIR}/" "${INSTANCE_NAME}:${remoteDir}/"`, | ||
| { encoding: "utf-8", timeout: 120_000 }, | ||
| ); |
There was a problem hiding this comment.
Don't rsync the whole checkout to the remote VM.
"${REPO_DIR}/" includes untracked local files, so this can upload .env*, .npmrc, ad-hoc keys, and other developer-only artifacts to the Brev instance. Prefer syncing tracked files only, or at minimum expand the exclude list to cover common secret files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@test/e2e/brev-e2e.test.js` around lines 135 - 138, The rsync call using
"${REPO_DIR}/" copies untracked developer files (e.g. .env*, .npmrc, keys);
update the sync to only transfer tracked files or explicitly exclude common
secret patterns: replace the execSync rsync invocation that references REPO_DIR/
(the execSync call around variables REPO_DIR, INSTANCE_NAME, remoteDir) with one
of two options — either generate a list of tracked files via git (e.g. use `git
-C "${REPO_DIR}" ls-files -z` piped into rsync --from0 --files-from=- ...) so
only tracked files are synced, or expand the rsync --exclude list to include
sensitive filenames/patterns (.env*, .npmrc, *.pem, id_rsa*, *.key, .ssh/,
.netrc, etc.) to prevent uploading secrets.
| OPENCLAW_AGENT_PREFIX="${OPENCLAW_AGENT_PREFIX:-nemoclaw-start}" | ||
| AGENT_LAUNCHER="" | ||
| [ -n "$OPENCLAW_AGENT_PREFIX" ] && AGENT_LAUNCHER="${OPENCLAW_AGENT_PREFIX} " | ||
| SESSION_ID="${SKILL_VERIFY_SESSION_ID:-skill-verify-$(date +%s)-${RANDOM}-${RANDOM}-${RANDOM}}" |
There was a problem hiding this comment.
Validate or escape SKILL_VERIFY_SESSION_ID before interpolating it into the remote shell.
SESSION_ID is copied straight into both the lock-file removal fragment and the final openclaw agent command. A caller-supplied value containing ', /, or shell metacharacters can break the command or point the cleanup at the wrong path. Mirror the SKILL_ID validation here or shell-escape it before building remote_cmd.
Suggested fix
SESSION_ID="${SKILL_VERIFY_SESSION_ID:-skill-verify-$(date +%s)-${RANDOM}-${RANDOM}-${RANDOM}}"
+case "$SESSION_ID" in
+ *[!A-Za-z0-9._-]* | "") die "SKILL_VERIFY_SESSION_ID may only contain [A-Za-z0-9._-]" ;;
+esacAlso applies to: 62-66
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@test/e2e/e2e-cloud-experimental/features/skill/verify-sandbox-skill-via-agent.sh`
at line 30, SESSION_ID is set from SKILL_VERIFY_SESSION_ID and then interpolated
into remote_cmd and the lock-file removal, which is unsafe; mirror the SKILL_ID
validation used elsewhere (or explicitly shell-escape SKILL_VERIFY_SESSION_ID)
before assigning SESSION_ID so the value contains only allowed characters (e.g.,
alphanumerics, dashes, underscores) or is properly quoted/escaped, then use that
sanitized SESSION_ID in the lock-file removal fragment and in the openclaw agent
invocation to prevent injection or path confusion; update references to
SESSION_ID, remote_cmd, and any lock-file paths to use the sanitized value.
| { | ||
| test: { | ||
| name: "e2e-brev", | ||
| include: ["test/e2e/brev-e2e.test.js"], | ||
| // Only run when explicitly targeted: npx vitest run --project e2e-brev | ||
| enabled: !!process.env.BREV_API_TOKEN, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Vitest project enabled option configuration
💡 Result:
In Vitest, there is no direct "enabled" option for projects to toggle them on/off in configuration. Projects are defined in the root test.projects array as globs, paths to config files, or inline objects. All defined projects run by default when executing vitest. To effectively "disable" a project: - Remove it from the projects array, or - Use CLI --project to run only specific projects by name (set via test.name), e.g., vitest --project unit --project e2e. Example configuration enabling multiple projects (from official docs): import { defineConfig } from 'vitest/config' export default defineConfig({ test: { projects: [ 'packages/', // Glob matching project directories { extends: true, test: { name: 'unit', include: ['/.unit.test.ts'] } } ] } }) Similar to browser.enabled: true for browser mode (requires provider setup), projects are enabled by inclusion in the array. Use include: [] or exclude: ['/*'] in a project config to skip test discovery if needed, though no explicit "enabled: false" exists. Run all: vitest Run specific: vitest --project unit
Citations:
- 1: https://vitest.dev/config/
- 2: https://v3.vitest.dev/guide/projects
- 3: https://main.vitest.dev/guide/projects
- 4: https://vitest.dev/guide/projects
- 5: https://howtotestfrontend.com/resources/vitest-config-projects
- 6: https://main.vitest.dev/config
Remove the invalid enabled property; Vitest does not support this configuration option for projects.
The enabled property at line 27 is not a valid Vitest project configuration option and will be silently ignored. Projects in Vitest are controlled by their presence in the test.projects array—all defined projects run by default. To conditionally run this project only when BREV_API_TOKEN is available, either:
- Dynamically include/exclude this project from the
test.projectsarray based on the environment variable, or - Remove it from configuration and use the CLI flag
vitest --project e2e-brevto run it explicitly when needed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vitest.config.ts` around lines 22 - 29, The project object that has name
"e2e-brev" contains an invalid property enabled which Vitest ignores; remove the
enabled property and instead conditionally include the project in the
test.projects array based on process.env.BREV_API_TOKEN (or remove the project
and run it via the CLI). Locate the test.projects array in vitest.config.ts and
either (a) wrap the "e2e-brev" project entry in a conditional that only adds it
when process.env.BREV_API_TOKEN is truthy, or (b) delete the "enabled" key and
keep the project static and rely on explicit CLI invocation (vitest --project
e2e-brev).
|
✨ Thanks for submitting this PR with a detailed summary, it addresses a bug with the policy presets and proposes a fix to improve the user experience of NemoClaw, which could enhance the usability of the platform. |
The policy presets step shows all presets with ○/● bullets, but suggested presets (npm, pypi, auto-detected tokens) were displayed with the same empty ○ bullet as non-suggested ones, making it unclear which presets would be applied by default. Include suggested presets in the filled ● bullet condition so they appear visually pre-selected, matching the prompt behavior. Fixes NVIDIA#706 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
a0bc8ca to
0558e8f
Compare
…777) ## Summary Fixes #706 Suggested policy presets (e.g. `npm`, `pypi`) now display with a filled `●` bullet instead of an empty `○`, matching the prompt behavior that applies them by default. ## Details **Before** (all presets show `○`): ``` ○ npm — npm and Yarn registry access (suggested) ○ pypi — Python Package Index (PyPI) access (suggested) ``` **After** (suggested presets show `●`): ``` ● npm — npm and Yarn registry access (suggested) ● pypi — Python Package Index (PyPI) access (suggested) ``` One-line change in `bin/lib/onboard.js`: include `suggestions` array in the marker condition alongside `applied`. ## Test plan - [x] Verified `npm test` — no new test failures - [ ] Visual: run `nemoclaw onboard`, observe step 7 — suggested presets should show `●` 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Policy preset suggestions now display as selected in the interactive listing before being officially applied. This change improves visual feedback, making it clearer to users which presets are recommended for their configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Temrjan <x.temrjan@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…VIDIA#777) ## Summary Fixes NVIDIA#706 Suggested policy presets (e.g. `npm`, `pypi`) now display with a filled `●` bullet instead of an empty `○`, matching the prompt behavior that applies them by default. ## Details **Before** (all presets show `○`): ``` ○ npm — npm and Yarn registry access (suggested) ○ pypi — Python Package Index (PyPI) access (suggested) ``` **After** (suggested presets show `●`): ``` ● npm — npm and Yarn registry access (suggested) ● pypi — Python Package Index (PyPI) access (suggested) ``` One-line change in `bin/lib/onboard.js`: include `suggestions` array in the marker condition alongside `applied`. ## Test plan - [x] Verified `npm test` — no new test failures - [ ] Visual: run `nemoclaw onboard`, observe step 7 — suggested presets should show `●` 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Policy preset suggestions now display as selected in the interactive listing before being officially applied. This change improves visual feedback, making it clearer to users which presets are recommended for their configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Temrjan <x.temrjan@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…VIDIA#777) ## Summary Fixes NVIDIA#706 Suggested policy presets (e.g. `npm`, `pypi`) now display with a filled `●` bullet instead of an empty `○`, matching the prompt behavior that applies them by default. ## Details **Before** (all presets show `○`): ``` ○ npm — npm and Yarn registry access (suggested) ○ pypi — Python Package Index (PyPI) access (suggested) ``` **After** (suggested presets show `●`): ``` ● npm — npm and Yarn registry access (suggested) ● pypi — Python Package Index (PyPI) access (suggested) ``` One-line change in `bin/lib/onboard.js`: include `suggestions` array in the marker condition alongside `applied`. ## Test plan - [x] Verified `npm test` — no new test failures - [ ] Visual: run `nemoclaw onboard`, observe step 7 — suggested presets should show `●` 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Policy preset suggestions now display as selected in the interactive listing before being officially applied. This change improves visual feedback, making it clearer to users which presets are recommended for their configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Temrjan <x.temrjan@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Fixes #706
Suggested policy presets (e.g.
npm,pypi) now display with a filled●bullet instead of an empty○, matching the prompt behavior that applies them by default.Details
Before (all presets show
○):After (suggested presets show
●):One-line change in
bin/lib/onboard.js: includesuggestionsarray in the marker condition alongsideapplied.Test plan
npm test— no new test failuresnemoclaw onboard, observe step 7 — suggested presets should show●🤖 Generated with Claude Code
Summary by CodeRabbit
Signed-off-by: Temrjan x.temrjan@gmail.com