2026 05 27 0hlj - #6977
Conversation
Split NVIDIA Build (nvapi-*) and Inference Hub (sk-*) env vars, load keys from ~/.nemoclaw/secrets.env, route Ultra via inference-api, and fix Tavily on rebuild resume.
Keep Tavily/Telegram support alongside upstream Brave egress verification and provider profile registration.
Removed unnecessary details about Dual NVIDIA keys and Secrets file while retaining key features for clarity.
Updated the README.fork.md to include new information and images.
Set toolSearch false for Ultra and document web_search usage so Tavily runs instead of failing tool_search_code.
Introduce the nemoclaw-cpu Helm chart with metrics-server-based autoscaling, install/recover/reset/load-test scripts, and documentation for running Inference Hub agents on MicroK8s (min 1, max 7 replicas per 8 vCPU).
Added an image and recovery instructions to README.
Introduce nemoclaw-gpu (Ollama + agent, DCGM gpu_utilization_percent HPA) with load-test and ops scripts tuned for 4× L40S. Split deploy guides into README-gpu/README-cpu and refresh the CPU chart helpers.
Removed 'GPU' from the title of the deployment README.
Updated images in README-gpu.md to include width and height attributes for better display.
Updated image syntax and added additional screenshots in the README for GPU deployment.
Updated README for GPU deployment with HPA details.
Signed-off-by: maggiezha <42832776+maggiezha@users.noreply.github.com>
📝 WalkthroughWalkthroughThe PR adds CPU and GPU Kubernetes Helm deployments with HPA, metrics, load testing, recovery tooling, and operational documentation. It also adds Nemotron Ultra routing, secure local credential staging, Tavily web-search support, Telegram configuration handling, related examples, and tests. ChangesKubernetes deployment and HPA
Inference, credentials, and web search
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
PR Review Advisor — Blocking findings reportedAdvisor assessment: Blockers require maintainer review Model lanes
Nemotron output stays in workflow artifacts and does not change the assessment above. E2E guidanceAdvisory only. E2E / PR Gate selects and runs jobs independently. Recommended E2E: Blockers
|
Added port 9000 for the CPU deployment section.
Removed the third port entry from the table.
Signed-off-by: maggiezha <42832776+maggiezha@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 5
Note
Due to the large number of review comments, Critical 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 (2)
deploy/helm/nemoclaw-cpu/files/agent-metrics.mjs (1)
1-83: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winCI blocks this new
.mjsfile — must resolve before merge.The Codebase Growth Guardrails check explicitly fails on this file: "blocked JavaScript source file added (.mjs)." This needs to be resolved — either by getting this file type allow-listed with maintainer sign-off, or by using an already-permitted extension/location for the module — before this PR can pass CI.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/files/agent-metrics.mjs` around lines 1 - 83, Resolve the Codebase Growth Guardrails failure for the new module by either moving the metrics implementation to an already-permitted module extension/location or obtaining the required maintainer allow-list approval for .mjs. Preserve the exported recordLlmLatency and llmMetricsLines API and all existing metrics behavior.Source: Pipeline failures
src/lib/onboard/policy-selection.ts (1)
209-214: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winReplace the stale preset instead of only deleting it during resume.
For example, a recorded
bravepreset with active Tavily configuration is filtered out here, but this function never addstavilybefore returning. Resume can therefore reapply no web-search egress policy. Add the resolved provider preset when web search is enabled and cover provider switching with a regression test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/onboard/policy-selection.ts` around lines 209 - 214, Update the resume policy flow around isStaleBuiltinWebSearchPolicyPreset and clampedRecordedPolicyPresets so a stale recorded web-search preset is replaced with the currently resolved provider preset when web search is enabled, rather than only filtered out. Preserve existing pruning behavior for other presets, and add a regression test covering switching providers such as brave to tavily during resume.
🟠 Major comments (46)
deploy/helm/nemoclaw-cpu/templates/hpa.yaml-1-2 (1)
1-2: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winMissing SPDX license headers. These three new files omit the SPDX header that the repo's other new files in this PR (e.g.
values.yaml, the shell scripts) consistently include.
deploy/helm/nemoclaw-cpu/templates/hpa.yaml#L1-L2: add a# SPDX-FileCopyrightText:/# SPDX-License-Identifier:header before the Helm template directives.deploy/helm/nemoclaw-cpu/observability.md#L1-L2: add an HTML-comment SPDX header (<!-- SPDX-FileCopyrightText: ... -->/<!-- SPDX-License-Identifier: ... -->) before the title.deploy/helm/nemoclaw-cpu/monitoring/prometheus-adapter-values.yaml#L1-L2: add a#-comment SPDX header before the existing usage comment.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/templates/hpa.yaml` around lines 1 - 2, All three new files are missing the repository-required SPDX headers. Add the standard two-line SPDX header before existing content in deploy/helm/nemoclaw-cpu/templates/hpa.yaml using # comments, deploy/helm/nemoclaw-cpu/observability.md using an HTML comment, and deploy/helm/nemoclaw-cpu/monitoring/prometheus-adapter-values.yaml using # comments; preserve the Helm directives, title, and usage comment after the headers.Source: Coding guidelines
README.md-6-7 (1)
6-7: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winPersonal fork/branch reference left in main README.
This callout points to a personal fork (
maggiezha/NemoClaw) and branch2026-05-27-0hlj, which matches this PR's own source branch. This reads like a leftover self-referential note rather than intentional documentation, and it will be broken/meaningless once this PR merges and the branch is deleted.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` around lines 6 - 7, Remove the personal fork and branch callout from the main README, including the reference to README.fork.md, so no PR-specific or self-referential documentation remains.deploy/README.md-1-2 (1)
1-2: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winMissing SPDX license headers on new files.
As per coding guidelines,
**/*.{ts,tsx,js,mjs,cjs,py,sh,yaml,yml,json,md,mdx,css,svg}files must include the appropriate SPDX license header (HTML comment for Markdown,#comment for YAML). These four new files added in this PR are all missing it:
deploy/README.md#L1-L2: add an HTML-comment SPDX header (<!-- SPDX-FileCopyrightText: ... -->/<!-- SPDX-License-Identifier: Apache-2.0 -->) before the H1.deploy/helm/nemoclaw-cpu/monitoring/kube-prometheus-microk8s.yaml#L1-L2: add a#-comment SPDX header above the existing top comment lines.deploy/helm/nemoclaw-cpu/templates/configmap.yaml#L1-L2: add a#-comment SPDX header aboveapiVersion: v1.deploy/helm/nemoclaw-gpu/templates/hpa.yaml#L1-L2: add a#-comment SPDX header above the{{- if .Values.autoscaling.enabled }}line.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/README.md` around lines 1 - 2, Add the repository-standard SPDX copyright and Apache-2.0 license headers to all four new files: use HTML comments before the heading in deploy/README.md, and # comment headers at the top of deploy/helm/nemoclaw-cpu/monitoring/kube-prometheus-microk8s.yaml, deploy/helm/nemoclaw-cpu/templates/configmap.yaml, and deploy/helm/nemoclaw-gpu/templates/hpa.yaml, preserving each file’s existing content and template directives.Source: Coding guidelines
deploy/helm/nemoclaw-gpu/templates/servicemonitor.yaml-1-1 (1)
1-1: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd SPDX headers to the new YAML sources.
deploy/helm/nemoclaw-gpu/templates/servicemonitor.yaml#L1-L1: add the Apache-2.0 SPDX header before the Helm directive.deploy/helm/nemoclaw-cpu/templates/namespace.yaml#L1-L1: add the Apache-2.0 SPDX header before the Helm directive.deploy/helm/nemoclaw-gpu/values-step2-hpa.yaml#L1-L1: add the Apache-2.0 SPDX header before the existing comments.As per coding guidelines, “Every source file must include the appropriate SPDX license header.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/templates/servicemonitor.yaml` at line 1, Add the standard Apache-2.0 SPDX header before the Helm directive in deploy/helm/nemoclaw-gpu/templates/servicemonitor.yaml and deploy/helm/nemoclaw-cpu/templates/namespace.yaml; add the same header before the existing comments in deploy/helm/nemoclaw-gpu/values-step2-hpa.yaml.Source: Coding guidelines
deploy/helm/nemoclaw-cpu/scripts/hpa-reset.sh-139-143 (1)
139-143: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winReturn failure when the rollout fails.
After
hpa_common_wait_rolloutfails, the script diagnoses the deployment but continues and exits successfully. Exit nonzero after diagnostics so automation does not treat an unsuccessful reset as complete.Proposed fix
if ! hpa_common_wait_rollout "${DEPLOYMENT}" "${NAMESPACE}" "${ROLLOUT_TIMEOUT}"; then hpa_common_diagnose_rollout "${NAMESPACE}" "${DEPLOYMENT}" + exit 1 fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/scripts/hpa-reset.sh` around lines 139 - 143, Update the WAIT_ROLLOUT failure branch around hpa_common_wait_rollout to invoke hpa_common_diagnose_rollout first, then exit with a nonzero status so an unsuccessful rollout reset is reported as failed.nemoclaw-blueprint/blueprint.yaml-57-60 (1)
57-60: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winCorrect the default NVIDIA model ID.
The model identifier has a duplicated
nvidia/namespace; NVIDIA documentsnvidia/llama-3.1-nemotron-ultra-253b-v1.Suggested change
- model: "nvidia/nvidia/llama-3.1-nemotron-ultra-253b-v1" + model: "nvidia/llama-3.1-nemotron-ultra-253b-v1"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nemoclaw-blueprint/blueprint.yaml` around lines 57 - 60, Update the default model value in the Nemotron Ultra configuration to use the documented single-namespace identifier `nvidia/llama-3.1-nemotron-ultra-253b-v1`, removing the duplicated `nvidia/` segment while leaving the endpoint and credential_env unchanged.Source: Coding guidelines
nemoclaw-blueprint/policies/presets/tavily.yaml-16-18 (1)
16-18: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRestrict the Tavily path allowlist.
The preset is declared for Search API access, but
"/**"permits the allowed Node andcurlprocesses to issue GET and POST requests to every endpoint onapi.tavily.com. Allow only the required paths, such as/search, or explicitly enumerate any additional endpoints required by the integration.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nemoclaw-blueprint/policies/presets/tavily.yaml` around lines 16 - 18, Restrict the GET and POST rules in the Tavily preset’s rules allowlist to the specific API paths required by the Search API integration, such as /search, instead of permitting /**. Enumerate any additional required endpoints explicitly while preserving the existing method permissions.Source: Path instructions
docs/security/credential-storage.mdx-40-43 (1)
40-43: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winClarify the host-disk storage claim.
The page says provider credentials are not persisted to host disk, but this new section instructs users to persist API keys in
~/.nemoclaw/secrets.env. Qualify the earlier claim as referring to gateway-stored credentials and explicitly state that the optional local secrets file is host-persisted and must be protected.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/security/credential-storage.mdx` around lines 40 - 43, Update the credential-storage documentation to qualify the earlier no-host-disk-persistence claim as applying only to gateway-stored credentials, and explicitly state that the optional ~/.nemoclaw/secrets.env file persists API keys on the host and requires protection with the documented file and directory permissions.deploy/helm/nemoclaw-cpu/files/cpu-spin-worker.mjs-1-11 (1)
1-11: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftResolve the codebase-growth guardrail failure before merging.
CI explicitly rejects this newly added
.mjssource file, so the PR remains red regardless of runtime correctness. Use an already-approved mechanism for the worker, or update the guardrail through the repository’s accepted process and update any chart consumer if the path changes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/files/cpu-spin-worker.mjs` around lines 1 - 11, Replace the newly added cpu-spin worker source in the deploy chart with an already-approved worker mechanism, or follow the repository’s accepted process to allow this file type and update all chart consumers if its path changes. Keep the worker behavior used by the CPU spin/HPA testing flow intact and ensure the codebase-growth guardrail passes.Source: Pipeline failures
deploy/helm/README.md-1-1 (1)
1-1: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd SPDX headers to all new Helm Markdown files.
All three files omit the required HTML-comment SPDX header.
deploy/helm/README.md#L1-L1: insert the copyright and Apache-2.0 SPDX header before the title.deploy/helm/nemoclaw-gpu/README.md#L1-L1: insert the same header before the title.deploy/helm/nemoclaw-cpu/README.md#L1-L1: insert the same header before the title.As per coding guidelines, “Every source file must include the appropriate SPDX license header; use HTML comments for Markdown.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/README.md` at line 1, Add the required HTML-comment copyright and Apache-2.0 SPDX header before the title in deploy/helm/README.md, deploy/helm/nemoclaw-gpu/README.md, and deploy/helm/nemoclaw-cpu/README.md.Source: Coding guidelines
scripts/generate-openclaw-config.py-869-897 (1)
869-897: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winReplace the managed instruction when the provider changes.
Configuring Brave and later Tavily appends both backend instructions to
AGENTS.md. Switching back then returns early because the old Brave phrase remains, leaving contradictory guidance. Replace one marker-delimited managed block instead of checking onlyusage_line.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/generate-openclaw-config.py` around lines 869 - 897, Update _append_web_search_answer_instruction to manage a single marker-delimited instruction block in AGENTS.md. Detect and replace the existing managed block whenever the configured provider changes, rather than returning based only on usage_line; preserve unrelated AGENTS.md content and append the block when none exists.scripts/generate-openclaw-config.py-831-833 (1)
831-833: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winReject unsupported providers instead of silently switching to Brave.
A typo such as
tavliygenerates a Brave configuration and credential reference even though Tavily was requested.Proposed fix
provider = (env.get("NEMOCLAW_WEB_SEARCH_PROVIDER") or "brave").strip().lower() -if provider not in ("brave", "tavily"): - provider = "brave" +if provider not in {"brave", "tavily"}: + raise ValueError( + "NEMOCLAW_WEB_SEARCH_PROVIDER must be either 'brave' or 'tavily'" + )As per coding guidelines, “Do not add configuration, fallback, migration, compatibility, or extension layers without a current requirement.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/generate-openclaw-config.py` around lines 831 - 833, Update the provider validation in the configuration-generation flow around the provider variable to reject unsupported non-empty values instead of replacing them with "brave". Preserve the default to Brave only when NEMOCLAW_WEB_SEARCH_PROVIDER is unset or blank, and raise the existing appropriate configuration error for values other than "brave" or "tavily".Source: Coding guidelines
deploy/helm/nemoclaw-gpu/files/agent-metrics.mjs-6-6 (1)
6-6: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winValidate
LLM_LATENCY_WINDOW_SIZEbefore using it.A nonnumeric value becomes
NaN, making the eviction condition permanently false and allowingllmDurationsMsto grow without bounds.Proposed fix
-const LLM_LATENCY_WINDOW = Number(process.env.LLM_LATENCY_WINDOW_SIZE || 128); +const configuredWindow = Number(process.env.LLM_LATENCY_WINDOW_SIZE ?? 128); +const LLM_LATENCY_WINDOW = + Number.isSafeInteger(configuredWindow) && configuredWindow > 0 ? configuredWindow : 128;Also applies to: 22-23
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/files/agent-metrics.mjs` at line 6, Validate the value used to initialize LLM_LATENCY_WINDOW before applying it to the llmDurationsMs eviction logic. Ensure nonnumeric, non-finite, or invalid window sizes fall back to the existing default so the eviction condition remains effective and the duration array stays bounded.src/lib/credentials/secrets-env.ts-126-134 (1)
126-134: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winEnforce secret-file ownership and permissions, and create it exclusively.
The loader accepts group/world-readable files, and
existsSyncfollowed bywriteFileSyncleaves a target-file race. On POSIX, reject files not owned by the current user or with any0o077bits, and create the template withflag: "wx".Proposed direction
const stat = fs.fstatSync(fd); if (!stat.isFile()) return lastStagedSecrets; +if ( + process.platform !== "win32" && + ((typeof process.getuid === "function" && stat.uid !== process.getuid()) || + (stat.mode & 0o077) !== 0) +) { + console.error(` Refusing to load ${secretsFile}: expected owner-only permissions.`); + return lastStagedSecrets; +} -fs.writeFileSync(secretsFile, SECRETS_ENV_TEMPLATE, { encoding: "utf-8", mode: 0o600 }); +fs.writeFileSync(secretsFile, SECRETS_ENV_TEMPLATE, { + encoding: "utf-8", + mode: 0o600, + flag: "wx", +});Also applies to: 184-189
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/credentials/secrets-env.ts` around lines 126 - 134, Harden the secrets-file loading and template-creation logic around the `stat` checks and existing file-write path: on POSIX, accept only regular files owned by the current user with no group/world permission bits (`0o077`). Replace the `existsSync`-then-`writeFileSync` creation flow with exclusive template creation using the write call’s `wx` flag, while preserving the existing fallback behavior when validation or creation fails.deploy/helm/nemoclaw-gpu/monitoring/dcgm-servicemonitor.yaml-1-2 (1)
1-2: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd the required SPDX license header.
Proposed fix
+# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + # Scrape NVIDIA DCGM exporter (already installed by microk8s enable gpu / GPU operator).As per coding guidelines, every YAML source file must include the appropriate SPDX license header.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/monitoring/dcgm-servicemonitor.yaml` around lines 1 - 2, Add the repository’s standard SPDX license header at the top of monitoring/dcgm-servicemonitor.yaml, before the existing DCGM exporter comment and apiVersion declaration. Preserve the manifest content unchanged.Source: Coding guidelines
README.fork.md-1-9 (1)
1-9: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winGet maintainer approval before adding fork-specific product documentation.
This brands the upstream repository around a personal fork, branch, and supported integration set, while the PR provides no accepted scope, ownership, lifecycle, compatibility, security, or validation decision. Remove it from the upstream PR or move it to the approved Community Solutions surface unless maintainers explicitly accept ownership.
As per coding guidelines, supported integrations and product surfaces require an accepted scope decision and defined ownership, lifecycle, compatibility, security, and validation expectations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.fork.md` around lines 1 - 9, Remove the fork-specific branding and integration claims from README.fork.md, including the personal fork, branch, and supported product surfaces, unless maintainer approval explicitly permits them. Keep upstream documentation references only, or move the content to the approved Community Solutions surface.Source: Coding guidelines
src/lib/onboard/web-search-flow.ts-90-98 (1)
90-98: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winSend the Tavily key as a bearer token.
Tavily’s search API expectsAuthorization: Bearer ...;api_keydoes not belong in the JSON body. This probe can reject valid credentials and block Tavily onboarding.Proposed fix
function validateTavilySearchApiKey(apiKey: string): CurlProbeResult { return runCurlProbe([ "-sS", "-H", "Content-Type: application/json", + "-H", + `Authorization: Bearer ${apiKey}`, "-d", - JSON.stringify({ api_key: apiKey, query: "ping", max_results: 1 }), + JSON.stringify({ query: "ping", max_results: 1 }), "https://api.tavily.com/search", ]); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/onboard/web-search-flow.ts` around lines 90 - 98, Update validateTavilySearchApiKey to send the API key via an Authorization Bearer header and remove api_key from the JSON request body, while preserving the existing search probe payload and endpoint.Source: MCP tools
deploy/helm/nemoclaw-cpu/templates/deployment.yaml-40-74 (1)
40-74: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winHarden the agent container security context.
The agent container still runs with the image defaults. Set
allowPrivilegeEscalation: false, drop all capabilities, enablereadOnlyRootFilesystem: true, and run as non-root. If anything needs writes, mount a writableemptyDironly where required.Proposed hardening
containers: - name: agent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/templates/deployment.yaml` around lines 40 - 74, Harden the agent container defined under the `containers` entry by adding a security context that disables privilege escalation, drops all Linux capabilities, enables `readOnlyRootFilesystem`, and enforces non-root execution. Review the agent runtime for required write locations and add narrowly scoped writable `emptyDir` mounts only where necessary, leaving the existing read-only `/app` mount unchanged.Source: Linters/SAST tools
deploy/helm/nemoclaw-gpu/files/load-generator.mjs-1-1 (1)
1-1: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftResolve the codebase growth guardrail failure.
CI explicitly rejects this newly added
.mjssource file. Move or convert it to an allowed location/type, or obtain the required maintainer-approved exception before merge.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/files/load-generator.mjs` at line 1, Resolve the codebase guardrail failure for the newly added load-generator source by moving it to an allowed location or converting it to an approved file type. If retaining the .mjs file is necessary, obtain and record the required maintainer-approved exception before merging; preserve the load generator’s behavior.Source: Pipeline failures
deploy/helm/nemoclaw-cpu/templates/_helpers.tpl-79-86 (1)
79-86: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winEnsure the HPA maximum cannot be lower than its minimum.
This helper can return
maxCpusor an explicitmaxReplicasbelow the value produced byhpaMinReplicas, yielding an invalid HPA. Fail template rendering for invalid bounds or enforcemaxReplicas >= minReplicas.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/templates/_helpers.tpl` around lines 79 - 86, Update the nemoclaw-cpu.hpaMaxReplicas helper to ensure its resolved maximum is never below the value from hpaMinReplicas. Validate explicit autoscaling.maxReplicas and the maxCpus-derived value, failing template rendering for invalid bounds or otherwise enforcing a maximum at least equal to hpaMinReplicas while preserving the existing fallback behavior..cursor/skills/nemoclaw-k8s-hpa/reference.md-1-1 (1)
1-1: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd the required Markdown SPDX header.
This new source file starts directly with its heading. Add the Apache-2.0 SPDX comments before it.
As per coding guidelines, every source file must include an appropriate SPDX license header, using HTML comments for Markdown.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.cursor/skills/nemoclaw-k8s-hpa/reference.md at line 1, Add the required Apache-2.0 SPDX license header as HTML comments at the beginning of the Markdown file, before the existing “NemoClaw K8s HPA — Reference” heading.Source: Coding guidelines
deploy/README-cpu.md-1-3 (1)
1-3: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd SPDX license headers to the new deployment READMEs. Both
deploy/README-cpu.mdanddeploy/README-gpu.mdare new files in this PR and omit the SPDX header required by the coding guidelines for Markdown files (HTML-comment style).
deploy/README-cpu.md#L1-L3: add an SPDX header before the H1, e.g.<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->/<!-- SPDX-License-Identifier: Apache-2.0 -->.deploy/README-gpu.md#L1-L4: add the same SPDX header block before the H1.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/README-cpu.md` around lines 1 - 3, Add the required two-line HTML SPDX header before the H1 in deploy/README-cpu.md at lines 1-3 and deploy/README-gpu.md at lines 1-4, using the standard NVIDIA copyright and Apache-2.0 license identifiers; leave the README content unchanged.Source: Coding guidelines
deploy/helm/nemoclaw-cpu/values-step2-hpa-performance.yaml-1-2 (1)
1-2: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd the required SPDX license header.
As per coding guidelines, “Every source file must include the appropriate SPDX license header.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/values-step2-hpa-performance.yaml` around lines 1 - 2, Add the repository’s standard SPDX license header at the beginning of the values-step2-hpa-performance.yaml file, before the existing performance HPA comments, while preserving the current configuration content.Source: Coding guidelines
deploy/helm/nemoclaw-gpu/templates/deployment.yaml-1-2 (1)
1-2: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd the required SPDX license header.
As per coding guidelines, “Every source file must include the appropriate SPDX license header.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/templates/deployment.yaml` around lines 1 - 2, Add the repository’s required SPDX license header at the beginning of the Kubernetes Deployment manifest, before the apiVersion declaration. Use the standard license identifier and comment format established by nearby source files.Source: Coding guidelines
deploy/helm/nemoclaw-gpu/scripts/cluster-recover.sh-13-13 (1)
13-13: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winRequire explicit opt-in before restarting MicroK8s.
RESTART_MICROK8Sdefaults to1, so release recovery stops the entire cluster and disrupts workloads in every namespace. Default this to0and require operators to opt in.Proposed fix
-RESTART_MICROK8S="${RESTART_MICROK8S:-1}" +RESTART_MICROK8S="${RESTART_MICROK8S:-0}"Also applies to: 31-36
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/scripts/cluster-recover.sh` at line 13, Update the RESTART_MICROK8S default in cluster-recover.sh from 1 to 0 so recovery does not restart MicroK8s unless operators explicitly opt in with RESTART_MICROK8S=1. Preserve the existing conditional restart behavior.deploy/helm/nemoclaw-cpu/scripts/hpa-common.sh-38-46 (1)
38-46: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winScope destructive recovery operations to the selected Helm release.
Both recovery paths operate namespace-wide instead of selecting resources owned by the release.
deploy/helm/nemoclaw-cpu/scripts/hpa-common.sh#L38-L46: add the release selector when patching and deleting pods.deploy/helm/nemoclaw-gpu/scripts/cluster-recover.sh#L19-L29: replace--allcleanup with the GPU release’s labels.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/scripts/hpa-common.sh` around lines 38 - 46, The recovery operations in hpa_common_clear_stuck_pods must target only the selected Helm release: add the release selector to both pod patching and deletion in deploy/helm/nemoclaw-cpu/scripts/hpa-common.sh lines 38-46. In deploy/helm/nemoclaw-gpu/scripts/cluster-recover.sh lines 19-29, replace namespace-wide --all cleanup with the GPU release’s labels.deploy/helm/nemoclaw-cpu/scripts/hpa-common.sh-60-72 (1)
60-72: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftDo not pass the inference API key through Helm arguments.
--set "inference.apiKey=${api_key}"exposes the credential in process arguments and may retain it in Helm release values. Reference a pre-created Kubernetes Secret instead of transporting the raw key through Helm.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/scripts/hpa-common.sh` around lines 60 - 72, Remove the raw inference.apiKey assignment from the helm_args array used by the HPA upgrade flow. Configure the chart to reference the pre-created Kubernetes Secret through the appropriate existing values key, ensuring the credential is never passed in process arguments or stored in Helm release values.src/lib/inference/provider-models.ts-11-15 (1)
11-15: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winKeep NVIDIA Build model discovery on the Integrate API URL.
BUILD_ENDPOINT_URLfeeds the Build/modelsvalidation path, while the added routing tests establish that NVIDIA Build usesNVIDIA_INTEGRATE_API_BASE_URLand Inference Hub usesNVIDIA_INFERENCE_API_BASE_URL. This change can reject valid Build models by querying the wrong catalog.Proposed fix
-import { NVIDIA_INFERENCE_API_BASE_URL } from "./config"; +import { NVIDIA_INTEGRATE_API_BASE_URL } from "./config"; -export const BUILD_ENDPOINT_URL = NVIDIA_INFERENCE_API_BASE_URL; +export const BUILD_ENDPOINT_URL = NVIDIA_INTEGRATE_API_BASE_URL;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/inference/provider-models.ts` around lines 11 - 15, Update BUILD_ENDPOINT_URL to use NVIDIA_INTEGRATE_API_BASE_URL so the Build /models validation path queries the Integrate catalog, while keeping NVIDIA_INFERENCE_API_BASE_URL for Inference Hub routing.deploy/helm/nemoclaw-cpu/scripts/hpa-common.sh-149-168 (1)
149-168: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winValidate the configured HPA maximum.
spec_maxand the expectedmaxare collected but never compared, so this function returns success even when the HPA exceeds the requested replica ceiling.Proposed fix
if [[ "${spec_min}" -lt "${min}" ]]; then echo "HPA spec.minReplicas=${spec_min} invalid" >&2 return 1 fi + + if [[ "${spec_max}" -gt "${max}" ]]; then + echo "HPA spec.maxReplicas=${spec_max} exceeds ${max}" >&2 + return 1 + fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/scripts/hpa-common.sh` around lines 149 - 168, Update the HPA validation flow around spec_max to compare the configured HPA maximum with the expected max value. Return a nonzero status and emit an error when spec_max exceeds max, while preserving the existing minimum validation and replica-floor enforcement behavior.deploy/helm/nemoclaw-gpu/templates/deployment.yaml-26-43 (1)
26-43: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftAdd pod and container securityContext settings. Both containers still use the default privileged context. Set
runAsNonRoot, drop capabilities, disable privilege escalation, and apply a runtime-default seccomp profile; if you switch to a read-only root filesystem, add writable mounts for/root/.ollamaand any temp paths the containers need.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/templates/deployment.yaml` around lines 26 - 43, Add pod-level and container-level securityContext settings in the deployment template: enforce non-root execution, use the runtime-default seccomp profile, drop Linux capabilities, and disable privilege escalation for both containers. If enabling read-only root filesystems, provide writable mounts for /root/.ollama and all required temporary paths.Source: Linters/SAST tools
scripts/test-tavily-flow.sh-68-70 (1)
68-70: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRestore the real
PATHbefore live Tavily validation.Line 69 keeps the fake
curlshim first inPATH, so--livereceives the shimmed200response instead of calling Tavily. Save the original path and restore it before Line 136.Proposed fix.
+ORIGINAL_PATH="$PATH" export HOME="$TMP" export PATH="$FAKE_BIN:$PATH" if [[ "$LIVE" -eq 1 ]]; then + export PATH="$ORIGINAL_PATH"Also applies to: 136-162
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/test-tavily-flow.sh` around lines 68 - 70, Save the original PATH before prepending FAKE_BIN in the test setup, then restore that saved PATH before the live Tavily validation block beginning at the live-mode flow. Keep the fake PATH for mock tests, but ensure the --live path invokes the real curl and reaches Tavily.deploy/helm/nemoclaw-gpu/scripts/hpa-load-test.sh-353-387 (1)
353-387: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winReturn a failure when either scaling assertion fails.
Missing scale-up only emits an error, while scale-down timing out is silently accepted. Track both outcomes and exit nonzero so automation can trust this load test.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/scripts/hpa-load-test.sh` around lines 353 - 387, The load test currently does not fail when scale-up or scale-down assertions fail. Update the scale-down loop to track whether replicas reached 1, retain the existing scale-up result via SCALE_UP_OK, and return a nonzero status when either outcome is unsuccessful; preserve the existing completion and HPA logging behavior.scripts/test-tavily-flow.sh-40-48 (1)
40-48: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not convert every Vitest failure into a skip.
Assertion failures and regressions currently produce a successful continuation labeled as a missing native binding. Only skip the known binding error; propagate all other failures.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/test-tavily-flow.sh` around lines 40 - 48, Update the Vitest invocation in the test flow so only the known missing native binding error is treated as a skip; preserve and propagate assertion failures, regressions, and other non-binding errors instead of labeling them unavailable. Use the command’s captured output and exit status to distinguish these cases, while retaining the existing success and continuation behavior for the recognized binding failure.deploy/helm/nemoclaw-gpu/files/agent-server.mjs-54-63 (1)
54-63: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winPreserve streaming semantics or reject streaming requests.
For
stream: true, this buffers the complete SSE response withtext()and rewrites its content type to JSON. Pipe the upstream body and headers, or reject streaming explicitly instead of claiming OpenAI compatibility.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/files/agent-server.mjs` around lines 54 - 63, Update the upstream request handling around the hubRes response to preserve streaming semantics: when the request body has stream enabled, forward the upstream response body and its relevant headers, including the SSE content type, without buffering through text() or rewriting it as JSON. Alternatively, explicitly reject streaming requests with an appropriate unsupported-status response; keep the existing JSON behavior for non-streaming requests.deploy/helm/nemoclaw-gpu/files/agent-server.mjs-24-30 (1)
24-30: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winBound the request body size.
readBodybuffers arbitrary client input in memory. Enforce a small maximum and return413before concatenating oversized bodies.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/files/agent-server.mjs` around lines 24 - 30, Update readBody to track accumulated request bytes against a small maximum while receiving chunks, reject or signal an oversized body with HTTP 413 before Buffer.concat, and stop processing further input once the limit is exceeded. Preserve normal UTF-8 body resolution for requests within the limit.deploy/helm/nemoclaw-gpu/files/agent-server.mjs-83-89 (1)
83-89: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRequire the configured model to exist before reporting Ready.
The
names.length > 0fallback marks the pod Ready when any unrelated model is installed. Remove that fallback so Kubernetes only routes traffic afterMODELis available.Proposed fix.
- const ok = - names.some((n) => n === MODEL || n.startsWith(`${want}:`) || n.includes(MODEL)) || - names.length > 0; + const ok = names.some( + (n) => n === MODEL || n.startsWith(`${want}:`) || n.includes(MODEL), + );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/files/agent-server.mjs` around lines 83 - 89, Update the readiness check in the model discovery flow to remove the names.length > 0 fallback from ok. Ensure ok is true only when a discovered model exactly matches MODEL, matches its desired tag, or contains MODEL, so the pod reports Ready only when the configured model is available.deploy/helm/nemoclaw-gpu/scripts/install-hpa.sh-62-77 (1)
62-77: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winDo not suppress Prometheus installation failures.
The
|| truepaths allow this script to continue with a failed or stale Prometheus release. Always reconcile the required values and propagate Helm or readiness failures.Proposed fix.
- if ! helm status "${PROM_RELEASE}" -n "${MONITORING_NS}" >/dev/null 2>&1; then - helm upgrade --install "${PROM_RELEASE}" prometheus-community/kube-prometheus-stack \ + helm upgrade --install "${PROM_RELEASE}" prometheus-community/kube-prometheus-stack \ --namespace "${MONITORING_NS}" \ --create-namespace \ -f "${PROM_VALUES}" \ --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false \ --set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false \ --set prometheus.prometheusSpec.ruleSelectorNilUsesHelmValues=false \ --timeout "${PROM_HELM_TIMEOUT}" \ - --wait >/dev/null 2>&1 || true - fi + --wait >/dev/null kubectl wait --for=condition=ready pod \ -l app.kubernetes.io/name=prometheus \ -n "${MONITORING_NS}" \ - --timeout=600s >/dev/null 2>&1 || true + --timeout=600s >/dev/null🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/scripts/install-hpa.sh` around lines 62 - 77, Update the Prometheus reconciliation block using helm upgrade --install so it always applies the required values instead of only running when helm status reports no release, and remove the || true suppressions from both the Helm command and kubectl wait. Ensure Helm installation/upgrade and Prometheus pod readiness failures propagate and stop the script.deploy/helm/nemoclaw-gpu/values.yaml-23-27 (1)
23-27: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winPin the Ollama image to an immutable version.
latestcan drift between nodes and restarts, so the same release may run different Ollama builds over time. Use a tested tag or digest here.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/values.yaml` around lines 23 - 27, Update the image tag under the ollama configuration to a tested, immutable Ollama version instead of latest; alternatively use a pinned image digest. Keep the existing repository and pullPolicy unchanged.deploy/helm/nemoclaw-gpu/scripts/install-hpa.sh-115-118 (1)
115-118: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMake the metrics-server gate conditional
deploy/helm/nemoclaw-gpu/scripts/install-hpa.sh:115-118blocks GPU-only HPA installs onmetrics.k8s.io, even though this script later requirescustom.metrics.k8s.ioforgpu_utilization_percent. Gate it only for resource-metric HPAs, or remove it here.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/scripts/install-hpa.sh` around lines 115 - 118, Update the metrics-server readiness check in install-hpa.sh so it runs only when resource-metric HPAs are being installed, and does not block GPU-only HPA installations that rely on custom.metrics.k8s.io. Preserve the existing failure message and exit behavior when the resource-metrics requirement applies.deploy/helm/nemoclaw-cpu/scripts/install-hpa.sh-80-80 (1)
80-80: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not report successful HPA operations when postconditions fail.
deploy/helm/nemoclaw-cpu/scripts/install-hpa.sh#L80-L80: propagate HPA bounds verification failure.deploy/helm/nemoclaw-gpu/scripts/hpa-reset.sh#L102-L105: diagnose the rollout failure, then exit nonzero.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/scripts/install-hpa.sh` at line 80, Propagate failures from hpa_common_verify_hpa_bounds in deploy/helm/nemoclaw-cpu/scripts/install-hpa.sh:80 by removing the unconditional success suppression. In deploy/helm/nemoclaw-gpu/scripts/hpa-reset.sh:102-105, diagnose rollout failure and exit with a nonzero status instead of reporting success.deploy/helm/nemoclaw-cpu/scripts/install-hpa.sh-49-49 (1)
49-49: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftDo not store the API key in Helm release values.
--set inference.apiKey=...exposes the credential through process arguments and Helm release metadata. Create or reference a Kubernetes Secret without passing the value through Helm.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/scripts/install-hpa.sh` at line 49, Remove the inference.apiKey value assignment from the Helm invocation in install-hpa.sh. Configure the deployment to reference an existing Kubernetes Secret for the API key, ensuring the credential is never passed through process arguments or stored in Helm release values.deploy/helm/nemoclaw-cpu/files/load-generator.mjs-1-147 (1)
1-147: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftThe new
.mjsdeployment sources are blocked by the same CI guardrail.
deploy/helm/nemoclaw-cpu/files/load-generator.mjs#L1-L147: move the load generator to an accepted source format/location or obtain an exception.deploy/helm/nemoclaw-cpu/files/agent-server.mjs#L1-L213: move the agent server to an accepted source format/location or obtain an exception.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/files/load-generator.mjs` around lines 1 - 147, The CI guardrail blocks both new .mjs deployment sources. Update deploy/helm/nemoclaw-cpu/files/load-generator.mjs (lines 1-147) and deploy/helm/nemoclaw-cpu/files/agent-server.mjs (lines 1-213) by moving them to an accepted source format or location, or obtain and apply the required CI exception; preserve their existing runtime behavior.Source: Pipeline failures
deploy/helm/nemoclaw-cpu/files/agent-server.mjs-26-32 (1)
26-32: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winEnforce a request-body size limit.
readBodyretains every chunk until the client finishes, allowing an accessible client to exhaust pod memory. Stop reading and return413after a fixed maximum size.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/files/agent-server.mjs` around lines 26 - 32, Update readBody to enforce a fixed maximum request-body size while accumulating chunks, tracking total bytes and stopping or rejecting once the limit is exceeded. Ensure the request is no longer consumed beyond the limit and the caller responds with HTTP 413, while preserving normal UTF-8 body handling for requests within the limit.deploy/helm/nemoclaw-gpu/scripts/hpa-reset.sh-46-52 (1)
46-52: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winRestrict destructive cleanup to this Helm release.
The script strips finalizers and force-deletes every pod in
NAMESPACE, including unrelated workloads when the configurable namespace is shared. Select only release-owned pods and do not clear unrelated finalizers.Also applies to: 71-77
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-gpu/scripts/hpa-reset.sh` around lines 46 - 52, Update clear_pod_finalizers and the corresponding pod-deletion logic to target only pods owned by this Helm release, using the release’s existing identifying labels or selectors rather than every pod in NAMESPACE. Apply the same ownership filter before stripping finalizers and force-deleting pods, while preserving the current cleanup behavior for matching release pods.deploy/helm/nemoclaw-cpu/files/agent-server.mjs-163-180 (1)
163-180: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winGate and bound the CPU load-test endpoint.
Any reachable client can invoke
/benchrepeatedly with arbitrarily largemsvalues, consuming worker threads indefinitely. Disable this endpoint by default or require authorization, and capms.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@deploy/helm/nemoclaw-cpu/files/agent-server.mjs` around lines 163 - 180, Update the /bench handling in the request server to disable the CPU load-test endpoint by default or require explicit authorization before invoking cpuSpinWorkers. Also cap the resolved spinMs value, including query, header, and fallback inputs, to a safe maximum while preserving the existing response behavior for authorized requests.Dockerfile-412-415 (1)
412-415: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUse the upstream model id everywhere this model is referenced.
nvidia/nvidia/llama-3.1-nemotron-ultra-253b-v1has one namespace too many; switch this ARG and the matchingNEMOCLAW_PRIMARY_MODEL_REFtonvidia/llama-3.1-nemotron-ultra-253b-v1.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Dockerfile` around lines 412 - 415, Update the NEMOCLAW_MODEL and NEMOCLAW_PRIMARY_MODEL_REF ARG values to use nvidia/llama-3.1-nemotron-ultra-253b-v1, removing the duplicate nvidia namespace while leaving NEMOCLAW_PROVIDER_KEY unchanged.
|
|
||
| NAMESPACE="${NAMESPACE:-nemoclaw}" | ||
| RELEASE="${RELEASE:-nemoclaw}" | ||
| RESTART_MICROK8S="${RESTART_MICROK8S:-1}" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Constrain recovery to the CPU release and make cluster restart opt-in.
The --all deletions remove GPU and unrelated workloads in the namespace, while RESTART_MICROK8S=1 stops every workload cluster-wide by default. Apply release labels to every deletion and default the restart flag to 0.
Proposed direction
-RESTART_MICROK8S="${RESTART_MICROK8S:-1}"
+RESTART_MICROK8S="${RESTART_MICROK8S:-0}"
-kubectl delete hpa -n "${NAMESPACE}" --all ...
-kubectl delete job -n "${NAMESPACE}" --all ...
-kubectl delete pods -n "${NAMESPACE}" --all ...
+kubectl delete hpa,job,pods -n "${NAMESPACE}" \
+ -l 'app.kubernetes.io/name=nemoclaw-cpu' ...Also applies to: 28-43
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@deploy/helm/nemoclaw-cpu/scripts/cluster-recover.sh` at line 13, Update the
cluster recovery script to default RESTART_MICROK8S to 0, and constrain every
kubectl deletion in the recovery flow to resources belonging to the CPU release
by applying the appropriate release label selector. Preserve the existing
recovery behavior while preventing GPU or unrelated namespace workloads from
being deleted and keeping cluster-wide MicroK8s restart opt-in.
| clear_pod_finalizers() { | ||
| local pod | ||
| for pod in $(kubectl get pods -n "${NAMESPACE}" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do | ||
| [[ -z "${pod}" ]] && continue | ||
| kubectl patch pod "${pod}" -n "${NAMESPACE}" -p '{"metadata":{"finalizers":null}}' --type=merge \ | ||
| >/dev/null 2>&1 || true | ||
| done |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Restrict destructive cleanup to this Helm release.
The script strips finalizers from and force-deletes every pod in NAMESPACE, despite documenting agent-pod cleanup. In a shared namespace, this terminates unrelated workloads and bypasses their cleanup controllers. Select only pods owned by ${RELEASE} and ${DEPLOYMENT}.
Also applies to: 90-93
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@deploy/helm/nemoclaw-cpu/scripts/hpa-reset.sh` around lines 64 - 70, Restrict
the destructive cleanup in clear_pod_finalizers and the corresponding
force-delete flow to pods owned by both ${RELEASE} and ${DEPLOYMENT}. Update the
kubectl pod selection or filtering before patching/deleting so unrelated pods in
NAMESPACE are never affected.
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| // Shared Prometheus helpers for agent /metrics (LLM latency, HTTP counters). | ||
|
|
||
| const LLM_LATENCY_WINDOW = Number(process.env.LLM_LATENCY_WINDOW_SIZE || 128); | ||
| const llmDurationsMs = []; | ||
| let llmDurationSumSec = 0; | ||
| let llmDurationCount = 0; | ||
| let llmRequestsOk = 0; | ||
| let llmRequestsError = 0; | ||
| const llmHistogramBucketsSec = [0.25, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300]; | ||
| const llmHistogramCounts = Array.from({ length: llmHistogramBucketsSec.length + 1 }, () => 0); | ||
|
|
||
| export function recordLlmLatency(durationMs, ok) { | ||
| const sec = Math.max(0, durationMs) / 1000; | ||
| llmDurationSumSec += sec; | ||
| llmDurationCount += 1; | ||
| if (ok) llmRequestsOk += 1; | ||
| else llmRequestsError += 1; | ||
|
|
||
| llmDurationsMs.push(durationMs); | ||
| if (llmDurationsMs.length > LLM_LATENCY_WINDOW) llmDurationsMs.shift(); | ||
|
|
||
| let bucketIdx = llmHistogramBucketsSec.findIndex((bound) => sec <= bound); | ||
| if (bucketIdx === -1) bucketIdx = llmHistogramBucketsSec.length; | ||
| for (let i = bucketIdx; i < llmHistogramCounts.length; i += 1) { | ||
| llmHistogramCounts[i] += 1; | ||
| } | ||
| } | ||
|
|
||
| function percentileMs(sorted, p) { | ||
| if (!sorted.length) return 0; | ||
| const idx = Math.ceil(sorted.length * p) - 1; | ||
| return sorted[Math.max(0, idx)]; | ||
| } | ||
|
|
||
| function llmLatencySnapshotMs() { | ||
| if (!llmDurationsMs.length) { | ||
| return { p50: 0, p95: 0, avg: 0 }; | ||
| } | ||
| const sorted = [...llmDurationsMs].sort((a, b) => a - b); | ||
| const sum = sorted.reduce((acc, v) => acc + v, 0); | ||
| return { | ||
| p50: percentileMs(sorted, 0.5), | ||
| p95: percentileMs(sorted, 0.95), | ||
| avg: sum / sorted.length, | ||
| }; | ||
| } | ||
|
|
||
| export function llmMetricsLines() { | ||
| const { p50, p95, avg } = llmLatencySnapshotMs(); | ||
| const lines = [ | ||
| "# HELP nemoclaw_llm_requests_total Chat/completions proxied to inference backend", | ||
| "# TYPE nemoclaw_llm_requests_total counter", | ||
| `nemoclaw_llm_requests_total{result="success"} ${llmRequestsOk}`, | ||
| `nemoclaw_llm_requests_total{result="error"} ${llmRequestsError}`, | ||
| "# HELP nemoclaw_llm_request_duration_seconds LLM chat/completions end-to-end proxy latency", | ||
| "# TYPE nemoclaw_llm_request_duration_seconds histogram", | ||
| ]; | ||
|
|
||
| for (let i = 0; i < llmHistogramBucketsSec.length; i += 1) { | ||
| lines.push( | ||
| `nemoclaw_llm_request_duration_seconds_bucket{le="${llmHistogramBucketsSec[i]}"} ${llmHistogramCounts[i]}`, | ||
| ); | ||
| } | ||
| lines.push( | ||
| `nemoclaw_llm_request_duration_seconds_bucket{le="+Inf"} ${llmHistogramCounts[llmHistogramCounts.length - 1]}`, | ||
| `nemoclaw_llm_request_duration_seconds_sum ${llmDurationSumSec}`, | ||
| `nemoclaw_llm_request_duration_seconds_count ${llmDurationCount}`, | ||
| "# HELP nemoclaw_llm_latency_p50_milliseconds Rolling p50 LLM latency (recent window)", | ||
| "# TYPE nemoclaw_llm_latency_p50_milliseconds gauge", | ||
| `nemoclaw_llm_latency_p50_milliseconds ${Math.round(p50)}`, | ||
| "# HELP nemoclaw_llm_latency_p95_milliseconds Rolling p95 LLM latency (recent window; HPA-friendly)", | ||
| "# TYPE nemoclaw_llm_latency_p95_milliseconds gauge", | ||
| `nemoclaw_llm_latency_p95_milliseconds ${Math.round(p95)}`, | ||
| "# HELP nemoclaw_llm_latency_avg_milliseconds Rolling average LLM latency (recent window)", | ||
| "# TYPE nemoclaw_llm_latency_avg_milliseconds gauge", | ||
| `nemoclaw_llm_latency_avg_milliseconds ${Math.round(avg)}`, | ||
| ); | ||
| return lines; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔴 Critical | 🏗️ Heavy lift
Resolve the codebase-growth guardrail failure.
CI explicitly rejects this newly added .mjs source file. Convert it to an allowed source format/location or obtain the required guardrail approval before merge.
🧰 Tools
🪛 GitHub Actions: CI / Codebase Growth Guardrails / 0_codebase-growth-guardrails.txt
[error] 1-1: CI check failed: blocked JavaScript source file added (.mjs). File marked as added.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@deploy/helm/nemoclaw-gpu/files/agent-metrics.mjs` around lines 1 - 82, The
new agent-metrics.mjs source file violates the repository’s codebase-growth
guardrail. Convert this metrics implementation to an approved source format or
allowed location, preserving the exported recordLlmLatency and llmMetricsLines
APIs and their behavior; otherwise obtain the required guardrail approval before
merging.
Source: Pipeline failures
| #!/usr/bin/env node | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| // GPU agent pod: health + Prometheus metrics + OpenAI-compatible proxy to local Ollama. | ||
|
|
||
| import http from "node:http"; | ||
| import { llmMetricsLines, recordLlmLatency } from "./agent-metrics.mjs"; | ||
|
|
||
| const PORT = Number(process.env.PORT || 8081); | ||
| const BASE_URL = (process.env.INFERENCE_BASE_URL || "http://127.0.0.1:11434/v1").replace(/\/$/, ""); | ||
| const OLLAMA_BASE = (process.env.OLLAMA_BASE_URL || "http://127.0.0.1:11434").replace(/\/$/, ""); | ||
| const MODEL = process.env.INFERENCE_MODEL || ""; | ||
|
|
||
| let inflight = 0; | ||
| let totalRequests = 0; | ||
| let inferenceReachable = 0; | ||
| let inferenceCache = { ok: false, at: 0 }; | ||
| const INFERENCE_CACHE_MS = Number(process.env.INFERENCE_READY_CACHE_MS || 15_000); | ||
| let inferenceReadyEver = false; | ||
| let inferenceFailStreak = 0; | ||
| const INFERENCE_FAIL_MAX = Number(process.env.INFERENCE_FAIL_MAX || 8); | ||
|
|
||
| function readBody(req) { | ||
| return new Promise((resolve, reject) => { | ||
| const chunks = []; | ||
| req.on("data", (c) => chunks.push(c)); | ||
| req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); | ||
| req.on("error", reject); | ||
| }); | ||
| } | ||
|
|
||
| async function proxyChatCompletions(req, res) { | ||
| let raw; | ||
| try { | ||
| raw = await readBody(req); | ||
| } catch { | ||
| res.writeHead(400, { "content-type": "text/plain" }); | ||
| res.end("bad request\n"); | ||
| return; | ||
| } | ||
| let body; | ||
| try { | ||
| body = raw ? JSON.parse(raw) : {}; | ||
| } catch { | ||
| res.writeHead(400, { "content-type": "text/plain" }); | ||
| res.end("invalid json\n"); | ||
| return; | ||
| } | ||
| if (!body.model) body.model = MODEL; | ||
| const llmStart = performance.now(); | ||
| let llmOk = false; | ||
| try { | ||
| const hubRes = await fetch(`${BASE_URL}/chat/completions`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(body), | ||
| signal: AbortSignal.timeout(300_000), | ||
| }); | ||
| llmOk = hubRes.ok; | ||
| const text = await hubRes.text(); | ||
| res.writeHead(hubRes.status, { "content-type": "application/json" }); | ||
| res.end(text); | ||
| } catch (err) { | ||
| res.writeHead(502, { "content-type": "application/json" }); | ||
| res.end(JSON.stringify({ error: String(err) })); | ||
| } finally { | ||
| recordLlmLatency(performance.now() - llmStart, llmOk); | ||
| } | ||
| } | ||
|
|
||
| async function checkInference() { | ||
| const now = Date.now(); | ||
| if (now - inferenceCache.at < INFERENCE_CACHE_MS) return inferenceCache.ok; | ||
| try { | ||
| const res = await fetch(`${OLLAMA_BASE}/api/tags`, { | ||
| signal: AbortSignal.timeout(10_000), | ||
| }); | ||
| if (!res.ok) { | ||
| inferenceCache = { ok: false, at: now }; | ||
| return false; | ||
| } | ||
| const data = await res.json(); | ||
| const names = (data.models || []).map((m) => m.name || m.model || ""); | ||
| const want = MODEL.split(":")[0]; | ||
| const ok = | ||
| names.some((n) => n === MODEL || n.startsWith(`${want}:`) || n.includes(MODEL)) || | ||
| names.length > 0; | ||
| if (ok) { | ||
| inferenceReadyEver = true; | ||
| inferenceFailStreak = 0; | ||
| inferenceCache = { ok: true, at: now }; | ||
| return true; | ||
| } | ||
| inferenceFailStreak += 1; | ||
| if (inferenceReadyEver && (inflight > 0 || inferenceFailStreak < INFERENCE_FAIL_MAX)) { | ||
| inferenceCache = { ok: true, at: now }; | ||
| return true; | ||
| } | ||
| inferenceCache = { ok: false, at: now }; | ||
| return false; | ||
| } catch { | ||
| inferenceFailStreak += 1; | ||
| if (inferenceReadyEver && (inflight > 0 || inferenceFailStreak < INFERENCE_FAIL_MAX)) { | ||
| inferenceCache = { ok: true, at: now }; | ||
| return true; | ||
| } | ||
| inferenceCache = { ok: false, at: now }; | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| function metricsText() { | ||
| return [ | ||
| "# HELP nemoclaw_http_requests_total Total HTTP requests to agent pod", | ||
| "# TYPE nemoclaw_http_requests_total counter", | ||
| `nemoclaw_http_requests_total ${totalRequests}`, | ||
| "# HELP nemoclaw_http_inflight_requests In-flight HTTP requests", | ||
| "# TYPE nemoclaw_http_inflight_requests gauge", | ||
| `nemoclaw_http_inflight_requests ${inflight}`, | ||
| "# HELP nemoclaw_inference_reachable 1 if local Ollama model is ready", | ||
| "# TYPE nemoclaw_inference_reachable gauge", | ||
| `nemoclaw_inference_reachable ${inferenceReachable}`, | ||
| ...llmMetricsLines(), | ||
| "", | ||
| ].join("\n"); | ||
| } | ||
|
|
||
| const server = http.createServer(async (req, res) => { | ||
| totalRequests += 1; | ||
| inflight += 1; | ||
| try { | ||
| if (req.url === "/healthz" || req.url === "/health") { | ||
| res.writeHead(200, { "content-type": "text/plain" }); | ||
| res.end("ok\n"); | ||
| return; | ||
| } | ||
| if (req.url === "/readyz" || req.url === "/ready") { | ||
| const ok = await checkInference(); | ||
| inferenceReachable = ok ? 1 : 0; | ||
| res.writeHead(ok ? 200 : 503, { "content-type": "text/plain" }); | ||
| res.end(ok ? "ready\n" : "ollama model not ready\n"); | ||
| return; | ||
| } | ||
| if (req.url === "/metrics") { | ||
| res.writeHead(200, { "content-type": "text/plain; version=0.0.4" }); | ||
| res.end(metricsText()); | ||
| return; | ||
| } | ||
| const pathOnly = (req.url || "").split("?")[0]; | ||
| if ( | ||
| (pathOnly === "/v1/chat/completions" || pathOnly === "/chat/completions") && | ||
| req.method === "POST" | ||
| ) { | ||
| await proxyChatCompletions(req, res); | ||
| return; | ||
| } | ||
| if (req.url === "/" && req.method === "GET") { | ||
| res.writeHead(200, { "content-type": "application/json" }); | ||
| res.end( | ||
| JSON.stringify({ | ||
| service: "nemoclaw-gpu-agent", | ||
| model: MODEL, | ||
| inferenceBaseUrl: BASE_URL, | ||
| ollamaBaseUrl: OLLAMA_BASE, | ||
| endpoints: ["/healthz", "/readyz", "/metrics", "POST /v1/chat/completions"], | ||
| note: "Local Ollama on GPU; scale replicas with kubectl or HPA (one pod per GPU)", | ||
| }), | ||
| ); | ||
| return; | ||
| } | ||
| res.writeHead(404); | ||
| res.end("not found\n"); | ||
| } finally { | ||
| inflight -= 1; | ||
| } | ||
| }); | ||
|
|
||
| server.listen(PORT, () => { | ||
| console.log(`nemoclaw-gpu-agent listening on :${PORT} model=${MODEL}`); | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔴 Critical | 🏗️ Heavy lift
Resolve the codebase-growth guardrail before merge.
CI explicitly rejects this added .mjs source file. Move the implementation to an accepted source location/language or obtain the required maintainer-approved exception.
🧰 Tools
🪛 GitHub Actions: CI / Codebase Growth Guardrails / 0_codebase-growth-guardrails.txt
[error] 1-1: CI check failed: blocked JavaScript source file added (.mjs). File marked as added.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@deploy/helm/nemoclaw-gpu/files/agent-server.mjs` around lines 1 - 181,
Resolve the codebase-growth guardrail for the agent server implementation by
moving the code from the added agent-server.mjs source file into an accepted
source location or language, preserving symbols such as proxyChatCompletions,
checkInference, metricsText, and the HTTP server behavior; alternatively, obtain
and record the required maintainer-approved exception before merging.
Source: Pipeline failures
| else | ||
| JOB_PARALLELISM="${JOB_PARALLELISM:-2}" | ||
| LOAD_MULTIPLIER="${LOAD_MULTIPLIER:-2}" | ||
| MAX_TOKENS="${MAX_TOKENS:-128}" | ||
| HPA_TARGET_GPU="${HPA_TARGET_GPU:-40}" | ||
| INFLIGHT_PER_GPU="${INFLIGHT_PER_GPU:-64}" | ||
| LOAD_COMPENSATION_SAFETY="${LOAD_COMPENSATION_SAFETY:-3}" | ||
| MAX_COMPENSATION="${MAX_COMPENSATION:-8}" | ||
| MAX_INFLIGHT_PER_POD="${MAX_INFLIGHT_PER_POD:-512}" | ||
| WARMUP_SEC="${WARMUP_SEC:-90}" | ||
| BOOTSTRAP_INFLIGHT="${BOOTSTRAP_INFLIGHT:-8}" | ||
| NEW_POD_WARMUP_PARALLEL="${NEW_POD_WARMUP_PARALLEL:-8}" | ||
| RAMP_SEC="${RAMP_SEC:-20}" | ||
| ESCALATE_INTERVAL_SEC="${ESCALATE_INTERVAL_SEC:-15}" | ||
| ESCALATE_FACTOR="${ESCALATE_FACTOR:-0.4}" | ||
| ESCALATE_MAX_MULT="${ESCALATE_MAX_MULT:-2}" | ||
| SCALE_UP_POLL_SEC="${SCALE_UP_POLL_SEC:-10}" | ||
| fi |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Initialize NEW_POD_RAMP_SEC for clusters with fewer than four GPUs.
The else branch leaves this variable unset, but Line 265 expands it under set -u, terminating the script before the Job is created.
Proposed fix.
else
JOB_PARALLELISM="${JOB_PARALLELISM:-2}"
+ NEW_POD_RAMP_SEC="${NEW_POD_RAMP_SEC:-0}"Also applies to: 264-265
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@deploy/helm/nemoclaw-gpu/scripts/hpa-load-test.sh` around lines 56 - 73,
Initialize NEW_POD_RAMP_SEC in the else branch alongside the other load-test
defaults, assigning the intended default value before the later expansion in the
Job creation flow. Ensure the variable is always set for clusters with fewer
than four GPUs so set -u cannot terminate the script.
|
close it as it was a testing with CodeRabbit team to make it work with my branch that was forked from main, the changes are from adding K8s autoscaling for NemoClaw, I will need to clean the code |
Summary
Related Issue
Changes
Type of Change
Quality Gates
Verification
Signed-off-by:line and every commit appears asVerifiedin GitHubpre-commit,commit-msg, andpre-pushhooks passed, ornpm run check:diffpassed when hooks were skipped or unavailablenpm testfor broad runtime/test-harness changes;npm run checkfor repo-wide validation/coverage changes — command/result:npm run docsbuilds without warnings (doc changes only)Signed-off-by: Your Name your-email@example.com
Summary by CodeRabbit