Skip to content

test: backfill mockable coverage for live-only behavior + guard against it - #6086

Merged
jyaunches merged 13 commits into
mainfrom
test/mock-recovery-reconcile-shell-units
Jul 6, 2026
Merged

test: backfill mockable coverage for live-only behavior + guard against it#6086
jyaunches merged 13 commits into
mainfrom
test/mock-recovery-reconcile-shell-units

Conversation

@prekshivyas

@prekshivyas prekshivyas commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two regressions from #5874 (fixed in #6065) only surfaced in live E2E targets that don't run on PR CI. This PR closes that class of gap: it audits the live suite for behavior-critical assertions that are cheaply mockable, backfills them as fast units that run on every PR, and adds a guard so pure-unit blocks can't hide in live files again.

What's here

1. The two direct #6065 regression fences (mocked shell-units)

  • reconcile: an explicit NEMOCLAW_MODEL_OVERRIDE survives a divergent gateway model and the stale in-file fallback; normal drift-correction still runs when unset.
  • guard recovery: the restore warning is mirrored into _NEMOCLAW_GATEWAY_LOG (the marker the crash-loop E2E polls), and stays silent when the chain is healthy.

2. High-priority mockable backfill (security/recovery class)

3. Medium/low backfill

  • ollama token-file lifecycle (0600 / persisted / divergent-repair); extra-placeholder-keys canonical placeholder + accepted-keys breadcrumb; hermes remove_stale_gateway_file symlink-safety; token-rotation selective-rebuild naming; _validate_port fail-closed; snapshot help branch.

4. Regression guard

  • scripts/checks/no-unit-blocks-in-live-e2e.ts bans the vitest it(...) primitive inside test/e2e/live/** (that glob is uncollected on PR CI, so such blocks never run). Wired into the checks registry with its own unit test.
  • Relocated the existing offenders (skill-agent + messaging-compatible-endpoint classifier blocks, plus the bare-test( unit cases in common-egress + openclaw-inference-switch) into importable test/e2e/support modules with PR-collected tests; the live tests import them unchanged.

Notes

  • Minimal behavior-preserving refactor to whatsapp-qr-compact.ts to export its pure helpers (the preload still auto-installs on require); tsconfig.runtime-preloads.json excludes the new co-located test from the shipped preload build.
  • nemoclaw-start.sh: made two possibly-empty-array iterations bash-3.2-safe via the existing "${arr[@]+...}" idiom so the shell-unit harnesses run on stock macOS bash.
  • Deferred (2 low-value items): the install.sh "Resolved install ref:" log assertion and the OpenClaw anthropic plain-baseUrl assertion both land in legacy-budget-capped test files where the growth guardrail forbids bumping the budget; the anthropic contract is already covered on the Hermes side and enforced live on the OpenClaw side.

Verification

  • Every new/changed test verified green individually across the cli, integration, and e2e-support projects.
  • npm run checks (incl. the new live-unit-block guard), test-file-size budget, gitleaks, and CLI typecheck all pass.
  • The full test-cli pre-commit hook was skipped locally only because it trips on pre-existing macOS bash 3.2 failures in untouched shell-harness suites (select/set -u); CI runs bash 5.x, where they are green.

Signed-off-by: Prekshi Vyas prekshiv@nvidia.com

Summary by CodeRabbit

  • Bug Fixes
    • Improved startup robustness for environment parsing and background launch behavior.
    • WhatsApp compact-QR rendering more consistently uses the compact “terminal” style.
    • Dashboard remote bind activates only when explicitly opted in via NEMOCLAW_DASHBOARD_BIND=0.0.0.0.
    • Tightened audit/config redaction to prevent secret leakage and omit gateway details.
  • Tests
    • Expanded coverage for guard-chain recovery warnings, model override precedence, Hermes env-boundary hardening, and proxy/policy correctness.
  • Chores
    • Added a CI safeguard to prevent unit-test primitives from being included in live E2E tests.

The two regressions #6065 fixed (NEMOCLAW_MODEL_OVERRIDE overwritten by
gateway reconcile; guard-chain recovery warning not reaching the gateway
log) were only caught by live E2E targets (runtime-overrides,
issue-2478-crash-loop-recovery) that do not run on PR CI. Both behaviors
are cheaply verifiable with mocked shell-units, so pin them in the PR gate:

- reconcile: an explicit override survives a divergent gateway model and
  the stale in-file fallback; normal drift-correction still runs when the
  override is unset (fences the early return itself).
- guard recovery: the restore warning is mirrored into _NEMOCLAW_GATEWAY_LOG
  as well as stderr, and stays silent when the chain is already complete.

Test-only; no production code change.

SKIP=test-cli: the cli+integration vitest hook trips on pre-existing macOS
bash 3.2 `set -u` empty-array failures in nemoclaw-start.sh unit harnesses
(green on CI bash 5.x); the new tests pass and stub those code paths.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR expands test coverage, extracts reusable E2E helpers, hardens shell/runtime handling for empty-array cases, adds a live E2E unit-block guard, and excludes runtime preload tests from TypeScript compilation.

Changes

Nemoclaw start and sandbox coverage

Layer / File(s) Summary
Guard recovery logging
test/nemoclaw-start-guard-recovery.test.ts
Adds tests for incomplete and complete guard-chain recovery, covering stderr output and gateway log creation.
Model override reconciliation
test/nemoclaw-start-reconcile.test.ts
Adds cases pinning NEMOCLAW_MODEL_OVERRIDE precedence, fallback behavior, and config hash updates.
Extra placeholder breadcrumb
test/nemoclaw-start-extra-placeholder-breadcrumb.test.ts, test/nemoclaw-start-extra-placeholder-breadcrumb-helpers.ts
Adds helper-backed tests for distinct placeholder rewriting and stderr breadcrumbs that omit refused co-submitted keys.
Config and audit redaction
src/lib/sandbox/config-get.test.ts, src/lib/shields/audit-format.test.ts
Adds redaction coverage for config output, gateway omission, and audit JSONL serialization of secret-shaped values.
Shell array safety
scripts/nemoclaw-start.sh
Adds empty-array guards and conditional expansions for Node options, preload validation, and auto-pair launch wiring.

Live E2E unit-block check

Layer / File(s) Summary
Live E2E scan and formatting
scripts/checks/no-unit-blocks-in-live-e2e.ts
Adds a checker that finds it(-style blocks in live E2E files and formats violations.
Check runner wiring
scripts/checks/run.ts
Wires the new checker into the existing checks list.
Checker tests
test/no-unit-blocks-in-live-e2e.test.ts
Adds tests for detection, non-detection, and violation formatting.

WhatsApp QR compact preload

Layer / File(s) Summary
Installer and patch helpers
src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts, src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact-test-helpers.ts
Refactors the preload hook into named helpers and an explicit installer.
Helper and loader tests
src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts
Adds tests for package detection, patching rules, idempotency, and module-loader interception.

Shared live E2E classifiers

Layer / File(s) Summary
Messaging endpoint classifiers
test/e2e/support/messaging-endpoint-classifiers.ts, test/e2e/support/messaging-endpoint-classifiers.test.ts, test/e2e/live/messaging-compatible-endpoint.test.ts
Extracts shared reply-token helpers and updates the live compatible-endpoint test to use them.
Skill-agent classifiers
test/e2e/support/skill-agent-classifiers.ts, test/e2e/support/skill-agent-classifiers.test.ts, test/e2e/live/skill-agent.test.ts
Extracts shared verification/failure classifiers and updates the live skill-agent test to import them.
Common egress helpers
test/e2e/live/common-egress-agent-helpers.ts, test/e2e/live/common-egress-agent.test.ts, test/e2e/support/common-egress-agent-helpers.test.ts
Moves OpenClaw/Hermes parsing and provider-validation skip logic into shared helpers and updates the live egress test to consume them.
Inference-switch helper extraction
test/e2e/live/openclaw-inference-switch-helpers.ts, test/e2e/live/openclaw-inference-switch.test.ts, test/e2e/support/openclaw-inference-switch-helpers.test.ts
Extracts the reply matcher into a shared helper and updates the live inference-switch test to import it.

Hermes secret-boundary hardening

Layer / File(s) Summary
Harness compatibility
test/hermes-env-secret-boundary-hardening.test.ts, test/hermes-start.test.ts
Adjusts shell harnesses to avoid empty-array expansion under set -u.
Value-shape discriminator tests
test/hermes-env-secret-boundary-hardening.test.ts
Adds tests for secret-shaped and non-secret-shaped values in env-file and process-env flows.
Gateway PID cleanup
test/hermes-gateway-pid-cleanup-helpers.ts, test/hermes-gateway-pid-cleanup.test.ts
Adds cleanup coverage for symlinks, stale files, missing paths, and dangling links.

Ollama auth proxy coverage

Layer / File(s) Summary
Proxy request flow
test/ollama-auth-proxy-handler-helpers.ts, test/ollama-auth-proxy-handler.test.ts
Adds a hermetic proxy integration suite covering auth, forwarding, malformed-token handling, and backend failures.
Proxy token recovery
test/ollama-proxy-recovery.test.ts
Adds restart and repair coverage for persisted proxy tokens, permissions, and stale-process recovery.

OpenClaw policy behavior

Layer / File(s) Summary
Python helper setup
test/openclaw-device-approval-policy.test.ts
Adds shared Python subprocess helpers and centralizes python3 availability checks.
Policy and recovery cases
test/openclaw-device-approval-policy.test.ts
Adds coverage for approval decisions, gateway env sanitization, and failed-scope recovery rejection paths.
Selective rebuild provider naming
test/credential-rotation.test.ts
Adds coverage that detectMessagingCredentialRotation returns only rotated provider names and the expected joined string.

Runtime preload compilation

Layer / File(s) Summary
Runtime preload exclude
tsconfig.runtime-preloads.json
Excludes runtime preload test files from compilation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • NVIDIA/NemoClaw#4771: Shares the Hermes secret-boundary harness and runtime validation paths exercised by the new tests.
  • NVIDIA/NemoClaw#4889: Shares the refresh_openclaw_provider_placeholders shell logic covered by the new extra-placeholder breadcrumb test.

Suggested labels: area: e2e, refactor

Suggested reviewers: cv, ericksoa

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main change: backfilling mockable live-only coverage and adding a guard against unit blocks in live E2E files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/mock-recovery-reconcile-shell-units

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

@github-code-quality

github-code-quality Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in the test/mock-recovery-r... branch is 96%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main test/mock-recovery-r... be41e69 +/-
nemoclaw/src/se...cret-scanner.ts 100%
nemoclaw/src/commands/slash.ts 100%
nemoclaw/src/li...bprocess-env.ts 100%
nemoclaw/src/bl...eprint/state.ts 98%
nemoclaw/src/onboard/config.ts 98%
nemoclaw/src/bl...int/snapshot.ts 97%
nemoclaw/src/bl...print/runner.ts 95%
nemoclaw/src/co...ration-state.ts 94%
nemoclaw/src/bl...ate-networks.ts 94%
nemoclaw/src/index.ts 94%

TypeScript / code-coverage/cli

The overall coverage in the test/mock-recovery-r... branch is 73%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main test/mock-recovery-r... be41e69 +/-
src/lib/shields...nsition-lock.ts 87%
src/lib/onboard/preflight.ts 83%
src/lib/actions...all/run-plan.ts 81%
src/lib/state/o...oard-session.ts 81%
src/lib/state/sandbox.ts 71%
src/lib/onboard...er-gpu-patch.ts 69%
src/lib/shields/index.ts 68%
src/lib/policy/index.ts 66%
src/lib/actions...licy-channel.ts 63%
src/lib/onboard.ts 29%

Updated July 06, 2026 19:32 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: cloud-onboard, messaging-compatible-endpoint, issue-4462-scope-upgrade-approval, whatsapp-qr-compact-e2e
Optional E2E: full-e2e, openclaw-inference-switch, skill-agent, common-egress-agent, macos-e2e

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • cloud-onboard (high): scripts/nemoclaw-start.sh is production startup/onboarding machinery. Run the hosted onboarding path to validate the changed shell array handling does not break sandbox startup or the installed runtime path.
  • messaging-compatible-endpoint (high): The changed runtimeSetup/nodePreloads code in nemoclaw-start.sh is used by channel runtime wiring. This live job validates a Telegram-enabled OpenClaw sandbox, inference.local compatible endpoint routing, gateway health, and an OpenClaw agent turn through the managed route.
  • issue-4462-scope-upgrade-approval (high): The auto-pair watcher launch command changed in nemoclaw-start.sh. This job exercises the real OpenClaw scope-upgrade approval path that depends on the watcher continuing to approve allowlisted late requests.
  • whatsapp-qr-compact-e2e (medium): The production WhatsApp compact-QR preload changed. Run the existing regression job that installs the bundled WhatsApp stack and verifies the real pairing QR is compact with the NemoClaw preload.

Optional E2E

  • full-e2e (high): Useful broad confidence for the changed startup script: validates a complete OpenClaw user journey including onboard, gateway readiness, inference.local, and an assistant turn.
  • openclaw-inference-switch (high): The live test and helper for this target were refactored, and the production startup change can affect running OpenClaw agent turns after route changes. Run for extra confidence if inference-switch reliability is in scope for the PR.
  • skill-agent (medium): The live skill-agent test was edited while moving classifier coverage into PR-collected support tests. A spot run would validate the modified live target still exercises real skill injection and OpenClaw agent behavior.
  • common-egress-agent (very high): The common-egress-agent live test was refactored to import shared helpers. It is not required for the production changes, but running it would validate the edited target still covers real network policy plus OpenClaw/Hermes agent egress behavior.
  • macos-e2e (medium): The shell changes explicitly address bash 3.2 empty-array behavior, which is relevant to macOS. This workflow is useful platform confidence, though the main required production coverage is provided by the Linux live jobs above.

New E2E recommendations

  • nemoclaw-start shell portability (medium): Existing live jobs validate the normal Linux sandbox path, but there is no focused live or hermetic E2E target that runs nemoclaw-start.sh under bash 3.2/non-root with empty NODE_OPTIONS, empty runtime preload targets, and an empty run_prefix to catch set -u array-expansion regressions.
    • Suggested test: Add a hermetic E2E-style shell portability target for nemoclaw-start empty-array cases, preferably runnable on macOS or with a pinned bash-3.2 fixture.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

E2E Target Recommendation

Required E2E targets: common-egress-agent, messaging-compatible-endpoint, openclaw-inference-switch, skill-agent, messaging-providers
Optional E2E targets: None

Dispatch required E2E targets:

  • gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=common-egress-agent
  • gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=messaging-compatible-endpoint
  • gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=openclaw-inference-switch
  • gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=skill-agent
  • gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=messaging-providers

Workflow run

Full E2E target advisor summary

E2E Target Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E targets

  • common-egress-agent: Focused free-standing E2E job wired for changed live test test/e2e/live/common-egress-agent.test.ts.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=common-egress-agent
  • messaging-compatible-endpoint: Focused free-standing E2E job wired for changed live test test/e2e/live/messaging-compatible-endpoint.test.ts.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=messaging-compatible-endpoint
  • openclaw-inference-switch: Focused free-standing E2E job wired for changed live test test/e2e/live/openclaw-inference-switch.test.ts.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=openclaw-inference-switch
  • skill-agent: Focused free-standing E2E job wired for changed live test test/e2e/live/skill-agent.test.ts.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=skill-agent
  • messaging-providers: The PR changes WhatsApp channel runtime preload logic and nemoclaw-start messaging runtime setup behavior; messaging-providers is the smallest wired live job that exercises WhatsApp channel add/rebuild/runtime integration in e2e.yaml.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=messaging-providers

Optional E2E targets

  • None.

Relevant changed files

  • scripts/nemoclaw-start.sh
  • src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact-test-helpers.ts
  • src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts
  • src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts
  • test/e2e/live/common-egress-agent-helpers.ts
  • test/e2e/live/common-egress-agent.test.ts
  • test/e2e/live/messaging-compatible-endpoint.test.ts
  • test/e2e/live/openclaw-inference-switch-helpers.ts
  • test/e2e/live/openclaw-inference-switch.test.ts
  • test/e2e/live/skill-agent.test.ts
  • test/e2e/support/common-egress-agent-helpers.test.ts
  • test/e2e/support/messaging-endpoint-classifiers.test.ts
  • test/e2e/support/messaging-endpoint-classifiers.ts
  • test/e2e/support/openclaw-inference-switch-helpers.test.ts
  • test/e2e/support/skill-agent-classifiers.test.ts
  • test/e2e/support/skill-agent-classifiers.ts

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Changes requested

Merge posture: Do not merge yet
Primary next action: Resolve or justify PRA-1: Source-of-truth review needed: Live E2E unit-block repository guard.
Open items: 0 required · 4 warnings · 0 suggestions · 8 test follow-ups
Since last review: 0 prior items resolved · 4 still apply · 0 new items found

Action checklist

  • PRA-1 Resolve or justify: Source-of-truth review needed: Live E2E unit-block repository guard
  • PRA-2 Resolve or justify: Source-of-truth review needed: Ciao `os.networkInterfaces()` crash-loop guard
  • PRA-3 Resolve or justify: New trusted repository check follows symlinks while walking PR-controlled live E2E files in scripts/checks/no-unit-blocks-in-live-e2e.ts:58
  • PRA-4 Resolve or justify: Ciao crash-loop guard behavior is still tested through stubs, not the packaged preload in test/nemoclaw-start-guard-recovery.test.ts:185
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Runtime validation
  • PRA-T3 Add or justify test follow-up: Runtime validation
  • PRA-T4 Add or justify test follow-up: Acceptance clause
  • PRA-T5 Add or justify test follow-up: Acceptance clause
  • PRA-T6 Add or justify test follow-up: Acceptance clause
  • PRA-T7 Add or justify test follow-up: Acceptance clause
  • PRA-T8 Add or justify test follow-up: Acceptance clause

Findings index

ID Severity Category Location Required action
PRA-1 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-2 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-3 Resolve/justify security scripts/checks/no-unit-blocks-in-live-e2e.ts:58 Use `lstatSync()` before recursion or file reads, skip symbolic links, and optionally track visited realpaths for directories. Keep the allowlist constrained to `test/e2e/live/**/*.test|spec.*` files.
PRA-4 Resolve/justify acceptance test/nemoclaw-start-guard-recovery.test.ts:185 Add a PR-collected test that loads the packaged Ciao guard as a Node preload before a fake ciao-like module calls `os.networkInterfaces()`, stubs the native call to throw a `uv_interface_addresses` or EPERM-style error, and asserts the initializer observes `{}` rather than crashing.
Review findings by urgency: 0 required fixes, 4 items to resolve/justify, 0 in-scope improvements

⚠️ Resolve or justify before merge

Investigate these in the current review; either fix them, explain why they are not applicable, or document the accepted risk.

PRA-1 Resolve/justify — Source-of-truth review needed: Live E2E unit-block repository guard

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: `test/no-unit-blocks-in-live-e2e.test.ts` covers pattern detection and formatting, but not `collectLiveUnitBlocks()` filesystem walking or symlink behavior.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: The source-of-truth concern is covered by the security finding: `walkFiles()` still uses `statSync()` and reads yielded files without symlink rejection.

PRA-2 Resolve/justify — Source-of-truth review needed: Ciao `os.networkInterfaces()` crash-loop guard

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Changed recovery tests cover guard-chain restaging and warning mirroring, but do not execute the packaged preload with a throwing `os.networkInterfaces()` call.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: This is covered by the acceptance finding: grep found no direct packaged-preload test in `test/nemoclaw-start-guard-recovery.test.ts`.

PRA-3 Resolve/justify — New trusted repository check follows symlinks while walking PR-controlled live E2E files

  • Location: scripts/checks/no-unit-blocks-in-live-e2e.ts:58
  • Category: security
  • Problem: `walkFiles()` calls `statSync(absPath)` before deciding whether to recurse or yield a test file, and `collectLiveUnitBlocks()` later reads yielded paths. `statSync()` follows symbolic links, so a symlink committed under `test/e2e/live/**` can redirect this trusted check outside the intended live-test subtree or into a directory cycle.
  • Impact: Because `scripts/checks/run.ts` now runs this check, PR-controlled repository contents can turn a lightweight trusted-code check into out-of-tree filesystem traversal, noisy disclosure of matching source lines, or denial-of-service in developer/check contexts. This weakens the trusted-code boundary for repository checks.
  • Recommended action: Use `lstatSync()` before recursion or file reads, skip symbolic links, and optionally track visited realpaths for directories. Keep the allowlist constrained to `test/e2e/live/**/*.test|spec.*` files.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read `scripts/checks/no-unit-blocks-in-live-e2e.ts` and confirm `walkFiles()` rejects `lstatSync(absPath).isSymbolicLink()` before any recursion or `readFileSync()`, with no remaining `statSync()`-only traversal path.
  • Missing regression test: Add `collectLiveUnitBlocks skips symlinked directories and symlinked test files without reading outside the live tree`: create a temp live tree with a symlink to its parent and a symlink to an out-of-tree `.test.ts` containing `it(...)`; assert collection terminates and reports no out-of-tree violation.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read `scripts/checks/no-unit-blocks-in-live-e2e.ts` and confirm `walkFiles()` rejects `lstatSync(absPath).isSymbolicLink()` before any recursion or `readFileSync()`, with no remaining `statSync()`-only traversal path.
  • Evidence: `scripts/checks/no-unit-blocks-in-live-e2e.ts` imports `statSync`, uses it inside `walkFiles()`, recurses on `stats.isDirectory()`, and `collectLiveUnitBlocks()` reads yielded files. `test/no-unit-blocks-in-live-e2e.test.ts` covers regex and formatting behavior but not `collectLiveUnitBlocks()` filesystem walking or symlinks.

PRA-4 Resolve/justify — Ciao crash-loop guard behavior is still tested through stubs, not the packaged preload

  • Location: test/nemoclaw-start-guard-recovery.test.ts:185
  • Category: acceptance
  • Problem: The changed recovery tests restage stub guard files and verify restore sequencing plus warning mirroring, but they do not load the actual `nemoclaw-blueprint/scripts/ciao-network-guard.js` before a ciao-like initializer calls `os.networkInterfaces()`.
  • Impact: A future change could keep the generic guard-chain recovery tests green while regressing the exact monkeypatch that prevents the OpenClaw gateway crash loop when `os.networkInterfaces()` throws in restricted sandbox network namespaces.
  • Recommended action: Add a PR-collected test that loads the packaged Ciao guard as a Node preload before a fake ciao-like module calls `os.networkInterfaces()`, stubs the native call to throw a `uv_interface_addresses` or EPERM-style error, and asserts the initializer observes `{}` rather than crashing.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect `test/nemoclaw-start-guard-recovery.test.ts` or a nearby PR-collected test for a spawned Node process or equivalent that requires `nemoclaw-blueprint/scripts/ciao-network-guard.js` before invoking a throwing `os.networkInterfaces()` path.
  • Missing regression test: Add `ciao network guard returns empty interfaces when os.networkInterfaces throws before ciao initialization`, using the real packaged guard and asserting exit code 0 plus `{}` output.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect `test/nemoclaw-start-guard-recovery.test.ts` or a nearby PR-collected test for a spawned Node process or equivalent that requires `nemoclaw-blueprint/scripts/ciao-network-guard.js` before invoking a throwing `os.networkInterfaces()` path.
  • Evidence: `nemoclaw-blueprint/scripts/ciao-network-guard.js` monkeypatches `os.networkInterfaces()` to return `{}` on failure. Grep found no `ciao-network-guard`, `os.networkInterfaces`, `uv_interface`, `EPERM`, or `spawnSync("node")` coverage in `test/nemoclaw-start-guard-recovery.test.ts`; the changed tests write stub `source-ciao.js` files and exercise recovery/warning emission.

💡 In-scope improvements

These are lower-risk, not throwaway. Prefer fixing them in this PR when they are local to changed code; defer only with rationale or a linked follow-up.

  • None.
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — Add `collectLiveUnitBlocks skips symlinked directories and symlinked test files without reading outside the live tree` for the new repository guard.. Most changed behavior is well covered by new fast unit/support tests, but this PR touches trusted repository checks, sandbox bootstrap shell, and runtime Node preloads where behavior depends on filesystem, shell, and runtime boundaries. The main missing runtime-style validations are the new check's filesystem walking and the packaged Ciao preload behavior.
  • PRA-T2 Runtime validation — Add `ciao network guard returns empty interfaces when os.networkInterfaces throws before ciao initialization` using `nemoclaw-blueprint/scripts/ciao-network-guard.js` as the real preload.. Most changed behavior is well covered by new fast unit/support tests, but this PR touches trusted repository checks, sandbox bootstrap shell, and runtime Node preloads where behavior depends on filesystem, shell, and runtime boundaries. The main missing runtime-style validations are the new check's filesystem walking and the packaged Ciao preload behavior.
  • PRA-T3 Runtime validation — Consider adding `ollama auth proxy returns 401 for unauthenticated /v1/models and 200 with a valid token` to mirror the literal [Brev][Security] Ollama auth proxy on port 11435 leaves Ollama-native /api/* endpoints unauthenticated #3338 namespace clause.. Most changed behavior is well covered by new fast unit/support tests, but this PR touches trusted repository checks, sandbox bootstrap shell, and runtime Node preloads where behavior depends on filesystem, shell, and runtime boundaries. The main missing runtime-style validations are the new check's filesystem walking and the packaged Ciao preload behavior.
  • PRA-T4 Acceptance clause[macOS][Brev][CLI&UX] Dashboard port 18789 hard-bound to 127.0.0.1 — no flag/env to bind 0.0.0.0 for remote-SSH-deployed hosts #3259 Expected Result: "Web UI auth flow accepts connections from non-localhost origins when the remote bind is opted-in" — add test evidence or identify existing coverage. This PR backfills fast dashboard bind decision coverage but does not change or directly exercise the web UI auth/origin flow in the diff reviewed here.
  • PRA-T5 Acceptance clause[macOS][Brev][CLI&UX] Dashboard port 18789 hard-bound to 127.0.0.1 — no flag/env to bind 0.0.0.0 for remote-SSH-deployed hosts #3259 Expected Result: "Remove the need for socat / manual SSH-tunnel workaround for SSH-deployed remote hosts" — add test evidence or identify existing coverage. The changed tests verify bind-target selection only; they do not run the remote-host browser access workflow.
  • PRA-T6 Acceptance clause[Brev][Security] Ollama auth proxy on port 11435 leaves Ollama-native /api/* endpoints unauthenticated #3338 Expected Result: "Both /api/tags and /v1/models return 401 without a token, and both return 200 with a valid token." — add test evidence or identify existing coverage. `test/ollama-auth-proxy-handler.test.ts` covers unauthenticated `/api/tags`, valid `/api/tags`, and valid `/v1/chat/completions`; it does not specifically assert `/v1/models` in the changed diff.
  • PRA-T7 Acceptance clauseOpenClaw CLI scope-upgrade approval deadlocks and forces openclaw agent into embedded fallback #4462 description: "The practical impact is that openclaw agent repeatedly falls back to embedded mode instead of using the gateway." — add test evidence or identify existing coverage. The changed fast tests cover the approval-policy gate and scope decisions, but the live agent fallback path itself remains outside the PR-collected unit boundary.
  • PRA-T8 Acceptance clause[Ubuntu 24.04][Onboard] local Ollama onboard fails auth proxy failed to start on :11435 #4820 Expected Result: "The Ollama auth proxy starts on :11435 and onboard completes; containers can reach Ollama through the proxy." — add test evidence or identify existing coverage. `test/ollama-auth-proxy-handler.test.ts` covers the non-ASCII auth crash class by asserting 401 and a subsequent successful valid request, while `test/ollama-proxy-recovery.test.ts` covers persisted-token restart behavior. The full onboard/container reachability workflow is not run in this PR-collected test set.
Since last review details

Current findings, using the urgency labels above:

PRA-1 Resolve/justify — Source-of-truth review needed: Live E2E unit-block repository guard

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: `test/no-unit-blocks-in-live-e2e.test.ts` covers pattern detection and formatting, but not `collectLiveUnitBlocks()` filesystem walking or symlink behavior.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: The source-of-truth concern is covered by the security finding: `walkFiles()` still uses `statSync()` and reads yielded files without symlink rejection.

PRA-2 Resolve/justify — Source-of-truth review needed: Ciao `os.networkInterfaces()` crash-loop guard

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Changed recovery tests cover guard-chain restaging and warning mirroring, but do not execute the packaged preload with a throwing `os.networkInterfaces()` call.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: This is covered by the acceptance finding: grep found no direct packaged-preload test in `test/nemoclaw-start-guard-recovery.test.ts`.

PRA-3 Resolve/justify — New trusted repository check follows symlinks while walking PR-controlled live E2E files

  • Location: scripts/checks/no-unit-blocks-in-live-e2e.ts:58
  • Category: security
  • Problem: `walkFiles()` calls `statSync(absPath)` before deciding whether to recurse or yield a test file, and `collectLiveUnitBlocks()` later reads yielded paths. `statSync()` follows symbolic links, so a symlink committed under `test/e2e/live/**` can redirect this trusted check outside the intended live-test subtree or into a directory cycle.
  • Impact: Because `scripts/checks/run.ts` now runs this check, PR-controlled repository contents can turn a lightweight trusted-code check into out-of-tree filesystem traversal, noisy disclosure of matching source lines, or denial-of-service in developer/check contexts. This weakens the trusted-code boundary for repository checks.
  • Recommended action: Use `lstatSync()` before recursion or file reads, skip symbolic links, and optionally track visited realpaths for directories. Keep the allowlist constrained to `test/e2e/live/**/*.test|spec.*` files.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read `scripts/checks/no-unit-blocks-in-live-e2e.ts` and confirm `walkFiles()` rejects `lstatSync(absPath).isSymbolicLink()` before any recursion or `readFileSync()`, with no remaining `statSync()`-only traversal path.
  • Missing regression test: Add `collectLiveUnitBlocks skips symlinked directories and symlinked test files without reading outside the live tree`: create a temp live tree with a symlink to its parent and a symlink to an out-of-tree `.test.ts` containing `it(...)`; assert collection terminates and reports no out-of-tree violation.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read `scripts/checks/no-unit-blocks-in-live-e2e.ts` and confirm `walkFiles()` rejects `lstatSync(absPath).isSymbolicLink()` before any recursion or `readFileSync()`, with no remaining `statSync()`-only traversal path.
  • Evidence: `scripts/checks/no-unit-blocks-in-live-e2e.ts` imports `statSync`, uses it inside `walkFiles()`, recurses on `stats.isDirectory()`, and `collectLiveUnitBlocks()` reads yielded files. `test/no-unit-blocks-in-live-e2e.test.ts` covers regex and formatting behavior but not `collectLiveUnitBlocks()` filesystem walking or symlinks.

PRA-4 Resolve/justify — Ciao crash-loop guard behavior is still tested through stubs, not the packaged preload

  • Location: test/nemoclaw-start-guard-recovery.test.ts:185
  • Category: acceptance
  • Problem: The changed recovery tests restage stub guard files and verify restore sequencing plus warning mirroring, but they do not load the actual `nemoclaw-blueprint/scripts/ciao-network-guard.js` before a ciao-like initializer calls `os.networkInterfaces()`.
  • Impact: A future change could keep the generic guard-chain recovery tests green while regressing the exact monkeypatch that prevents the OpenClaw gateway crash loop when `os.networkInterfaces()` throws in restricted sandbox network namespaces.
  • Recommended action: Add a PR-collected test that loads the packaged Ciao guard as a Node preload before a fake ciao-like module calls `os.networkInterfaces()`, stubs the native call to throw a `uv_interface_addresses` or EPERM-style error, and asserts the initializer observes `{}` rather than crashing.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect `test/nemoclaw-start-guard-recovery.test.ts` or a nearby PR-collected test for a spawned Node process or equivalent that requires `nemoclaw-blueprint/scripts/ciao-network-guard.js` before invoking a throwing `os.networkInterfaces()` path.
  • Missing regression test: Add `ciao network guard returns empty interfaces when os.networkInterfaces throws before ciao initialization`, using the real packaged guard and asserting exit code 0 plus `{}` output.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect `test/nemoclaw-start-guard-recovery.test.ts` or a nearby PR-collected test for a spawned Node process or equivalent that requires `nemoclaw-blueprint/scripts/ciao-network-guard.js` before invoking a throwing `os.networkInterfaces()` path.
  • Evidence: `nemoclaw-blueprint/scripts/ciao-network-guard.js` monkeypatches `os.networkInterfaces()` to return `{}` on failure. Grep found no `ciao-network-guard`, `os.networkInterfaces`, `uv_interface`, `EPERM`, or `spawnSync("node")` coverage in `test/nemoclaw-start-guard-recovery.test.ts`; the changed tests write stub `source-ciao.js` files and exercise recovery/warning emission.

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor (Nemotron Ultra) — Changes requested

Merge posture: Do not merge yet
Primary next action: Fix PRA-3: @ts-nocheck disables type safety on security-sensitive Module._load hook; then add or justify PRA-T1.
Open items: 1 required · 5 warnings · 8 suggestions · 8 test follow-ups
Since last review: 1 prior item resolved · 9 still apply · 3 new items found

Action checklist

  • PRA-3 Fix: @ts-nocheck disables type safety on security-sensitive Module._load hook in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1
  • PRA-1 Resolve or justify: Source-of-truth review needed: whatsapp-qr-compact.ts silent catch degrades to unpatched module
  • PRA-2 Resolve or justify: Source-of-truth review needed: config-get.test.ts require.cache mutation for mocking
  • PRA-4 Resolve or justify: Imprecise Module._load request filter matches unrelated modules in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:147
  • PRA-5 Resolve or justify: 9 overlapping PRs on nemoclaw-start.sh require merge verification in scripts/nemoclaw-start.sh:1855
  • PRA-12 Resolve or justify: Fragile CommonJS cache mutation for test mocking in src/lib/sandbox/config-get.test.ts:23
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Runtime validation
  • PRA-T3 Add or justify test follow-up: Runtime validation
  • PRA-T4 Add or justify test follow-up: Runtime validation
  • PRA-T5 Add or justify test follow-up: Runtime validation
  • PRA-T6 Add or justify test follow-up: Guard script lacks integration test for collectLiveUnitBlocks directory walk
  • PRA-T7 Add or justify test follow-up: Missing test for Module._load hook false-positive avoidance
  • PRA-T8 Add or justify test follow-up: Dashboard bind validation missing IPv6 address test cases
  • PRA-6 In-scope improvement: Guard deliberately only flags it(...) primitive, not bare test(...) unit cases in scripts/checks/no-unit-blocks-in-live-e2e.ts:38
  • PRA-7 In-scope improvement: Registry mock pattern adds scaffold overhead; pure function extraction would simplify in test/credential-rotation.test.ts:273
  • PRA-8 In-scope improvement: Monolith growth - extract dcode probe helpers to shared module in src/lib/actions/sandbox/snapshot.test.ts:994
  • PRA-9 In-scope improvement: Guard script lacks integration test for collectLiveUnitBlocks directory walk in test/no-unit-blocks-in-live-e2e.test.ts:1
  • PRA-10 In-scope improvement: Missing test for Module._load hook false-positive avoidance in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts:163
  • PRA-11 In-scope improvement: Dashboard bind validation missing IPv6 address test cases in src/lib/onboard/dashboard-access.test.ts:155
  • PRA-13 In-scope improvement: Silent catch in Module._load hook degrades to unpatched module without test coverage in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:143
  • PRA-14 In-scope improvement: Test uses require.cache mutation instead of Vitest mocking in src/lib/sandbox/config-get.test.ts:65

Findings index

ID Severity Category Location Required action
PRA-1 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-2 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-3 Required security src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1 Remove @ts-nocheck and fix all type errors. Add explicit types to all exported functions: hasOwn(mod: object, name: string): boolean, isQrcodePackage(mod: unknown): mod is { toString: Function; create: Function }, isQrcodeTerminalPackage(mod: unknown): mod is { generate: Function }, patchQrcode(mod: object): object, patchQrcodeTerminal(mod: object): object, resolvePatchedModule(request: string, loaded: unknown): unknown.
PRA-4 Resolve/justify security src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:147 Tighten the filter to match only actual package entry points: `request === 'qrcode' || request === 'qrcode-terminal' || /\[/\\\\\]qrcode\[/\\\\\]/.test(request) || /\[/\\\\\]qrcode-terminal\[/\\\\\]/.test(request)`. This preserves absolute-path matching for `import('qrcode')` while avoiding false positives.
PRA-5 Resolve/justify security scripts/nemoclaw-start.sh:1855 Verify the current diff against each overlapping PR using `git merge-tree` or GitHub's mergeability UI to ensure no textual or semantic conflicts. Confirm the bash 3.2 fixes don't conflict with other in-flight changes.
PRA-6 Improvement architecture scripts/checks/no-unit-blocks-in-live-e2e.ts:38 Accept the current design but document the known limitation in the guard's header comment (already present). Consider a follow-up to add a heuristic for `test(` blocks that have no async sandbox fixtures (e.g., no `{ host }`, `{ sandbox }`, `{ artifacts }` parameters), but only if false positives are manageable.
PRA-7 Improvement architecture test/credential-rotation.test.ts:273 Extract the core credential rotation detection logic into a pure function `detectRotationPure(planEntries, tokenDefs)` that returns { changed, changedProviders }. Simplify tests to call the pure function directly. If registry provides necessary context (e.g., caching), justify why the mock is needed.
PRA-8 Improvement architecture src/lib/actions/sandbox/snapshot.test.ts:994 Extract runProbeScriptWithProcesses, the probe script generator (capturedDcodeProbeScript), and the process line fixtures to a shared helper module (e.g., src/lib/actions/sandbox/snapshot-dcode-probe.ts or test/support/snapshot-dcode-probe.ts). Import in snapshot.test.ts.
PRA-9 Improvement tests test/no-unit-blocks-in-live-e2e.test.ts:1 Add an integration test in test/no-unit-blocks-in-live-e2e.test.ts that creates a temp directory with a mix of violating and non-violating test files, runs collectLiveUnitBlocks, and asserts the correct violations are reported.
PRA-10 Improvement tests src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts:163 Add test case: `it('does NOT patch unrelated module with qrcode in specifier', () => { const mod = { some: 'module' }; const result = resolvePatchedModule('my-qrcode-wrapper', mod); expect(result).toBe(mod); })` and similar for 'qrcode-generator', '@scope/qrcode-utils'.
PRA-11 Improvement tests src/lib/onboard/dashboard-access.test.ts:155 Add test case: `it.each(['::', '::1', '2001:db8::1'])('rejects IPv6 bind value %j', (value) => { ... })` to the negative value matrix.
PRA-12 Resolve/justify security src/lib/sandbox/config-get.test.ts:23 Rewrite using Vitest's `vi.hoisted` and `vi.mock` for more reliable module mocking, or at minimum add `vi.resetModules()` in afterEach to ensure clean state between tests.
PRA-13 Improvement security src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:143 Add a test case that forces a patch failure (e.g., by passing a module that throws during shape detection) and verifies the original module is returned unpatched.
PRA-14 Improvement tests src/lib/sandbox/config-get.test.ts:65 Migrate to `vi.hoisted` + `vi.mock` pattern for reliable mocking. This is a test-only refactor with no behavior change.

🚨 Required before merge

Address these before merging unless a maintainer explicitly overrides the advisor with rationale.

PRA-3 Required — @ts-nocheck disables type safety on security-sensitive Module._load hook

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1
  • Category: security
  • Problem: The file contains `// @ts-nocheck` at line 1, completely disabling TypeScript type checking for a runtime Module._load hook that patches qrcode/qrcode-terminal packages globally in the sandbox. This hook mutates loaded modules at require-time. Type safety is a critical defense against shape-detection bugs that could over-patch (mutating unrelated modules) or under-patch (leaving WhatsApp QR oversized on DGX Spark).
  • Impact: A shape-detection bug (e.g., false positive on isQrcodePackage matching a submodule) could mutate unrelated modules or fail to patch the real qrcode package, leaving the WhatsApp QR oversized on DGX Spark. Type errors that would catch this at compile time are suppressed.
  • Required action: Remove @ts-nocheck and fix all type errors. Add explicit types to all exported functions: hasOwn(mod: object, name: string): boolean, isQrcodePackage(mod: unknown): mod is { toString: Function; create: Function }, isQrcodeTerminalPackage(mod: unknown): mod is { generate: Function }, patchQrcode(mod: object): object, patchQrcodeTerminal(mod: object): object, resolvePatchedModule(request: string, loaded: unknown): unknown.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Run `npx tsc --noEmit src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts` and confirm no errors. Verify all 6 exported functions have explicit parameter and return types.
  • Missing regression test: Typecheck must pass on this file in CI. Ensure the project's typecheck configuration covers this file.
  • Done when: The required change is committed and verification passes: Run `npx tsc --noEmit src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts` and confirm no errors. Verify all 6 exported functions have explicit parameter and return types.
  • Evidence: Line 1 of whatsapp-qr-compact.ts contains `// @ts-nocheck`. The file exports 6 functions that are unit-tested in whatsapp-qr-compact.test.ts but have no explicit types.
Review findings by urgency: 1 required fix, 5 items to resolve/justify, 8 in-scope improvements

⚠️ Resolve or justify before merge

Investigate these in the current review; either fix them, explain why they are not applicable, or document the accepted risk.

PRA-1 Resolve/justify — Source-of-truth review needed: whatsapp-qr-compact.ts silent catch degrades to unpatched module

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: None - gap in test coverage
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: Line 143 in whatsapp-qr-compact.ts: catch (_e) { return loaded; }

PRA-2 Resolve/justify — Source-of-truth review needed: config-get.test.ts require.cache mutation for mocking

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Tests pass; behavior verified
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: Lines 65 and 103 in config-get.test.ts: delete require.cache[configModulePath]

PRA-4 Resolve/justify — Imprecise Module._load request filter matches unrelated modules

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:147
  • Category: security
  • Problem: The Module._load request filter uses `request.indexOf('qrcode') !== -1` which matches any module request containing 'qrcode' as a substring (e.g., 'my-qrcode-wrapper', 'qrcode-generator', '@scope/qrcode-utils'). While shape detection provides a second guard, the first filter should be precise to avoid unnecessary shape checks on unrelated modules and reduce attack surface.
  • Impact: Unrelated modules containing 'qrcode' in their path/specifier undergo unnecessary shape detection (hasOwn checks). In worst case, a malicious or buggy module with matching shape could be incorrectly patched.
  • Recommended action: Tighten the filter to match only actual package entry points: `request === 'qrcode' || request === 'qrcode-terminal' || /\[/\\\\\]qrcode\[/\\\\\]/.test(request) || /\[/\\\\\]qrcode-terminal\[/\\\\\]/.test(request)`. This preserves absolute-path matching for `import('qrcode')` while avoiding false positives.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Check the filter logic at line 147 in whatsapp-qr-compact.ts. Verify unit tests cover the Module._load hook path with absolute paths like `/tmp/app/node_modules/qrcode/lib/index.js`.
  • Missing regression test: Add a test case in whatsapp-qr-compact.test.ts where a module request like 'my-qrcode-wrapper' or 'qrcode-generator' reaches resolvePatchedModule and confirm it is NOT patched (shape detection fails and original module is returned).
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Check the filter logic at line 147 in whatsapp-qr-compact.ts. Verify unit tests cover the Module._load hook path with absolute paths like `/tmp/app/node_modules/qrcode/lib/index.js`.
  • Evidence: Line 147 in whatsapp-qr-compact.ts: `if (typeof request === "string" && request.indexOf("qrcode") !== -1) {`

PRA-5 Resolve/justify — 9 overlapping PRs on nemoclaw-start.sh require merge verification

PRA-12 Resolve/justify — Fragile CommonJS cache mutation for test mocking

  • Location: src/lib/sandbox/config-get.test.ts:23
  • Category: security
  • Problem: New test file config-get.test.ts uses `require()` to load TypeScript modules and mock `captureOpenshellCommand` by mutating `require.cache`. This works but relies on CommonJS cache mutation which can be brittle across test runs and Vitest's module isolation. The test stubs the openshell client before requiring the config module.
  • Impact: Test flakiness risk; mocks may not clean up properly between tests; Vitest's module isolation may not work as expected with manual cache manipulation.
  • Recommended action: Rewrite using Vitest's `vi.hoisted` and `vi.mock` for more reliable module mocking, or at minimum add `vi.resetModules()` in afterEach to ensure clean state between tests.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Check config-get.test.ts lines 23-45 - it uses `require.resolve`, deletes `require.cache`, and mutates `client.captureOpenshellCommand` directly.
  • Missing regression test: The test coverage itself is good - it tests credential redaction and gateway omission. The mocking approach just needs hardening.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Check config-get.test.ts lines 23-45 - it uses `require.resolve`, deletes `require.cache`, and mutates `client.captureOpenshellCommand` directly.
  • Evidence: Lines 23-45: require.resolve, delete require.cache[configModulePath], client.captureOpenshellCommand = ...

💡 In-scope improvements

These are lower-risk, not throwaway. Prefer fixing them in this PR when they are local to changed code; defer only with rationale or a linked follow-up.

PRA-6 Improvement — Guard deliberately only flags it(...) primitive, not bare test(...) unit cases

  • Location: scripts/checks/no-unit-blocks-in-live-e2e.ts:38
  • Category: architecture
  • Problem: The guard deliberately only flags `it(...)` primitive, not bare `test(...)` unit cases, because a live test using module-level helpers reads identically to a unit case. This is documented as a known limitation with zero false positives on `it(`. The design is intentional and reasonable.
  • Impact: Pure unit blocks using `test(...)` could still hide in live files, but the `it(...)` ban catches the observed regression pattern with zero false positives.
  • Suggested action: Accept the current design but document the known limitation in the guard's header comment (already present). Consider a follow-up to add a heuristic for `test(` blocks that have no async sandbox fixtures (e.g., no `{ host }`, `{ sandbox }`, `{ artifacts }` parameters), but only if false positives are manageable.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Review the guard's header comment (lines 1-35) which explains the design rationale. Confirm the test file test/no-unit-blocks-in-live-e2e.test.ts verifies `test(...)` is not flagged.
  • Missing regression test: None needed - this is an accepted design trade-off.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Guard header comment lines 1-35 explain the design. Test file verifies test(...) is not flagged.

PRA-7 Improvement — Registry mock pattern adds scaffold overhead; pure function extraction would simplify

  • Location: test/credential-rotation.test.ts:273
  • Category: architecture
  • Problem: The new 'selective-rebuild provider naming' describe block (lines 273-380) still uses the registry mock pattern (vi.spyOn(registry, 'getSandbox')). The core credential rotation detection logic remains coupled to the registry. Extracting a pure function that takes plan entries and tokenDefs as arguments would simplify the 11 tests and remove the registry dependency.
  • Impact: Tests are harder to read and maintain; registry coupling makes it difficult to verify pure logic in isolation.
  • Suggested action: Extract the core credential rotation detection logic into a pure function `detectRotationPure(planEntries, tokenDefs)` that returns { changed, changedProviders }. Simplify tests to call the pure function directly. If registry provides necessary context (e.g., caching), justify why the mock is needed.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check test/credential-rotation.test.ts lines 273-380 - all 6 new tests use vi.spyOn(registry, 'getSandbox').
  • Missing regression test: The new tests already cover the provider naming behavior. After extraction, the same tests would call the pure function directly.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: 6 new tests in credential-rotation.test.ts all use vi.spyOn(registry, 'getSandbox') mock pattern.

PRA-8 Improvement — Monolith growth - extract dcode probe helpers to shared module

  • Location: src/lib/actions/sandbox/snapshot.test.ts:994
  • Category: architecture
  • Problem: Monolith grew by 13 lines (981 → 994). The dcode probe helpers (runProbeScriptWithProcesses, capturedDcodeProbeScript, process line fixtures) are still inline in snapshot.test.ts rather than extracted to a shared module as previously suggested.
  • Impact: Large test file is harder to maintain; dcode probe logic duplicated if needed elsewhere.
  • Suggested action: Extract runProbeScriptWithProcesses, the probe script generator (capturedDcodeProbeScript), and the process line fixtures to a shared helper module (e.g., src/lib/actions/sandbox/snapshot-dcode-probe.ts or test/support/snapshot-dcode-probe.ts). Import in snapshot.test.ts.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Search for runProbeScriptWithProcesses and capturedDcodeProbeScript in snapshot.test.ts - they are defined inline and used only in this file.
  • Missing regression test: None - extraction is a refactoring; existing tests cover the behavior.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: snapshot.test.ts is 994 lines after change. dcode probe helpers defined inline.

PRA-9 Improvement — Guard script lacks integration test for collectLiveUnitBlocks directory walk

  • Location: test/no-unit-blocks-in-live-e2e.test.ts:1
  • Category: tests
  • Problem: The guard script's collectLiveUnitBlocks function walks the test/e2e/live directory but has no integration test that creates a temp directory with violating/non-violating files and asserts correct violations are reported. The existing tests only unit-test findLiveUnitBlocks with string inputs.
  • Impact: Directory traversal logic (walkFiles, file pattern matching) is untested against real filesystem.
  • Suggested action: Add an integration test in test/no-unit-blocks-in-live-e2e.test.ts that creates a temp directory with a mix of violating and non-violating test files, runs collectLiveUnitBlocks, and asserts the correct violations are reported.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check test/no-unit-blocks-in-live-e2e.test.ts - all tests call findLiveUnitBlocks directly with string sources. No test exercises collectLiveUnitBlocks with a real filesystem.
  • Missing regression test: Add integration test for collectLiveUnitBlocks directory walk with real temp files.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Test file only tests findLiveUnitBlocks with string inputs; collectLiveUnitBlocks is untested with real fs.

PRA-10 Improvement — Missing test for Module._load hook false-positive avoidance

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts:163
  • Category: tests
  • Problem: The test file has a positive test for absolute path patching (line 143-163) but lacks a negative test for false-positive avoidance. Modules like 'my-qrcode-wrapper', 'qrcode-generator', '@scope/qrcode-utils' should reach resolvePatchedModule but NOT be patched because shape detection fails.
  • Impact: False-positive patching of unrelated modules would not be caught by tests.
  • Suggested action: Add test case: `it('does NOT patch unrelated module with qrcode in specifier', () => { const mod = { some: 'module' }; const result = resolvePatchedModule('my-qrcode-wrapper', mod); expect(result).toBe(mod); })` and similar for 'qrcode-generator', '@scope/qrcode-utils'.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check whatsapp-qr-compact.test.ts - the 'Module._load hook path-segment matching' describe block only has one positive test. No negative tests for false-positive avoidance.
  • Missing regression test: Add negative test cases for false-positive avoidance in the Module._load hook.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Only one test in 'Module._load hook path-segment matching' describe block; tests absolute path patching but not false-positive avoidance.

PRA-11 Improvement — Dashboard bind validation missing IPv6 address test cases

  • Location: src/lib/onboard/dashboard-access.test.ts:155
  • Category: tests
  • Problem: The new 'NEMOCLAW_DASHBOARD_BIND remote-bind opt-in gate' describe block (lines 79-155) tests various invalid values but does not include IPv6 address test cases (e.g., '::', '::1', '2001:db8::1'). IPv6 bind values should be explicitly rejected to stay loopback.
  • Impact: IPv6 bind values could accidentally open remote bind if not explicitly rejected.
  • Suggested action: Add test case: `it.each(['::', '::1', '2001:db8::1'])('rejects IPv6 bind value %j', (value) => { ... })` to the negative value matrix.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check dashboard-access.test.ts lines 115-135 - the it.each matrix includes '0.0.0.0; rm -rf', '1.2.3.4', 'true', '10.0.0.5', ' 0.0.0.0', '0.0.0.0 ' but no IPv6 addresses.
  • Missing regression test: Add IPv6 bind value rejection test cases to the negative matrix.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: it.each matrix at lines 115-135 has 6 invalid values, none are IPv6 addresses.

PRA-13 Improvement — Silent catch in Module._load hook degrades to unpatched module without test coverage

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:143
  • Category: security
  • Problem: The resolvePatchedModule function wraps shape detection and patching in try/catch and silently returns the unpatched module on any error. While this is a safe degradation, there is no explicit test verifying this failure path behavior.
  • Impact: If patching fails due to unexpected module shape, the hook silently degrades - no test verifies this behavior or ensures it doesn't mask real bugs.
  • Suggested action: Add a test case that forces a patch failure (e.g., by passing a module that throws during shape detection) and verifies the original module is returned unpatched.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check whatsapp-qr-compact.ts line 143: `} catch (_e) { return loaded; }` - no test exercises this catch block.
  • Missing regression test: Add test for patch failure degradation path in whatsapp-qr-compact.test.ts.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Line 143 in whatsapp-qr-compact.ts: catch (_e) { return loaded; } - untested failure path.

PRA-14 Improvement — Test uses require.cache mutation instead of Vitest mocking

  • Location: src/lib/sandbox/config-get.test.ts:65
  • Category: tests
  • Problem: The config-get.test.ts uses `delete require.cache[configModulePath]` and mutates the openshell client's capture method directly. This is a workaround for Vitest's module isolation when testing CommonJS modules. While it works, it's fragile and should be migrated to proper Vitest mocking.
  • Impact: Test reliability risk; cache mutation can leak state between tests.
  • Suggested action: Migrate to `vi.hoisted` + `vi.mock` pattern for reliable mocking. This is a test-only refactor with no behavior change.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Lines 65 and 103 in config-get.test.ts: `delete require.cache[configModulePath]`
  • Missing regression test: Existing tests cover behavior; mocking refactor should not change coverage.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Two occurrences of delete require.cache[configModulePath] in config-get.test.ts
Simplification opportunities: 2 possible cuts, net -70 lines possible

These are safe simplification checks only. Do not remove validation, security controls, data-loss prevention, or required tests.

  • PRA-8 shrink (src/lib/actions/sandbox/snapshot.test.ts:994): runProbeScriptWithProcesses, capturedDcodeProbeScript, and process line fixtures (approx 80 lines)
    • Replacement: Import from new shared module snapshot-dcode-probe.ts
    • Net: -70 lines
    • Safety boundary: Existing dcode probe tests must continue to pass; no behavior change
  • PRA-14 stdlib (src/lib/sandbox/config-get.test.ts:65): Manual require.cache manipulation and direct client mutation
    • Replacement: vi.hoisted(() => { ... }) + vi.mock('../adapters/openshell/client', ...)
    • Net: 0 lines
    • Safety boundary: Test behavior must remain identical - credential redaction and gateway omission assertions
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — Integration test for collectLiveUnitBlocks with real temp directory and violating/non-violating files. Runtime/sandbox/infrastructure paths need behavioral runtime validation: scripts/checks/no-unit-blocks-in-live-e2e.ts (guard fs walk), scripts/nemoclaw-start.sh (bash 3.2 fix + merge verification), src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts (Module._load hook in sandbox), tsconfig.runtime-preloads.json (build-time exclusion).
  • PRA-T2 Runtime validation — False-positive avoidance test for resolvePatchedModule with my-qrcode-wrapper, qrcode-generator, @scope/qrcode-utils. Runtime/sandbox/infrastructure paths need behavioral runtime validation: scripts/checks/no-unit-blocks-in-live-e2e.ts (guard fs walk), scripts/nemoclaw-start.sh (bash 3.2 fix + merge verification), src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts (Module._load hook in sandbox), tsconfig.runtime-preloads.json (build-time exclusion).
  • PRA-T3 Runtime validation — IPv6 bind rejection tests for NEMOCLAW_DASHBOARD_BIND (::, ::1, 2001:db8::1). Runtime/sandbox/infrastructure paths need behavioral runtime validation: scripts/checks/no-unit-blocks-in-live-e2e.ts (guard fs walk), scripts/nemoclaw-start.sh (bash 3.2 fix + merge verification), src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts (Module._load hook in sandbox), tsconfig.runtime-preloads.json (build-time exclusion).
  • PRA-T4 Runtime validation — Patch failure degradation test for Module._load hook catch block. Runtime/sandbox/infrastructure paths need behavioral runtime validation: scripts/checks/no-unit-blocks-in-live-e2e.ts (guard fs walk), scripts/nemoclaw-start.sh (bash 3.2 fix + merge verification), src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts (Module._load hook in sandbox), tsconfig.runtime-preloads.json (build-time exclusion).
  • PRA-T5 Runtime validation — git merge-tree verification against 9 overlapping PRs on nemoclaw-start.sh. Runtime/sandbox/infrastructure paths need behavioral runtime validation: scripts/checks/no-unit-blocks-in-live-e2e.ts (guard fs walk), scripts/nemoclaw-start.sh (bash 3.2 fix + merge verification), src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts (Module._load hook in sandbox), tsconfig.runtime-preloads.json (build-time exclusion).
  • PRA-T6 Guard script lacks integration test for collectLiveUnitBlocks directory walk — Add an integration test in test/no-unit-blocks-in-live-e2e.test.ts that creates a temp directory with a mix of violating and non-violating test files, runs collectLiveUnitBlocks, and asserts the correct violations are reported.
  • PRA-T7 Missing test for Module._load hook false-positive avoidance — Add test case: `it('does NOT patch unrelated module with qrcode in specifier', () => { const mod = { some: 'module' }; const result = resolvePatchedModule('my-qrcode-wrapper', mod); expect(result).toBe(mod); })` and similar for 'qrcode-generator', '@scope/qrcode-utils'.
  • PRA-T8 Dashboard bind validation missing IPv6 address test cases — Add test case: `it.each(['::', '::1', '2001:db8::1'])('rejects IPv6 bind value %j', (value) => { ... })` to the negative value matrix.
Since last review details

Current findings, using the urgency labels above:

PRA-1 Resolve/justify — Source-of-truth review needed: whatsapp-qr-compact.ts silent catch degrades to unpatched module

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: None - gap in test coverage
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: Line 143 in whatsapp-qr-compact.ts: catch (_e) { return loaded; }

PRA-2 Resolve/justify — Source-of-truth review needed: config-get.test.ts require.cache mutation for mocking

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Tests pass; behavior verified
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: Lines 65 and 103 in config-get.test.ts: delete require.cache[configModulePath]

PRA-3 Required — @ts-nocheck disables type safety on security-sensitive Module._load hook

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1
  • Category: security
  • Problem: The file contains `// @ts-nocheck` at line 1, completely disabling TypeScript type checking for a runtime Module._load hook that patches qrcode/qrcode-terminal packages globally in the sandbox. This hook mutates loaded modules at require-time. Type safety is a critical defense against shape-detection bugs that could over-patch (mutating unrelated modules) or under-patch (leaving WhatsApp QR oversized on DGX Spark).
  • Impact: A shape-detection bug (e.g., false positive on isQrcodePackage matching a submodule) could mutate unrelated modules or fail to patch the real qrcode package, leaving the WhatsApp QR oversized on DGX Spark. Type errors that would catch this at compile time are suppressed.
  • Required action: Remove @ts-nocheck and fix all type errors. Add explicit types to all exported functions: hasOwn(mod: object, name: string): boolean, isQrcodePackage(mod: unknown): mod is { toString: Function; create: Function }, isQrcodeTerminalPackage(mod: unknown): mod is { generate: Function }, patchQrcode(mod: object): object, patchQrcodeTerminal(mod: object): object, resolvePatchedModule(request: string, loaded: unknown): unknown.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Run `npx tsc --noEmit src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts` and confirm no errors. Verify all 6 exported functions have explicit parameter and return types.
  • Missing regression test: Typecheck must pass on this file in CI. Ensure the project's typecheck configuration covers this file.
  • Done when: The required change is committed and verification passes: Run `npx tsc --noEmit src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts` and confirm no errors. Verify all 6 exported functions have explicit parameter and return types.
  • Evidence: Line 1 of whatsapp-qr-compact.ts contains `// @ts-nocheck`. The file exports 6 functions that are unit-tested in whatsapp-qr-compact.test.ts but have no explicit types.

PRA-4 Resolve/justify — Imprecise Module._load request filter matches unrelated modules

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:147
  • Category: security
  • Problem: The Module._load request filter uses `request.indexOf('qrcode') !== -1` which matches any module request containing 'qrcode' as a substring (e.g., 'my-qrcode-wrapper', 'qrcode-generator', '@scope/qrcode-utils'). While shape detection provides a second guard, the first filter should be precise to avoid unnecessary shape checks on unrelated modules and reduce attack surface.
  • Impact: Unrelated modules containing 'qrcode' in their path/specifier undergo unnecessary shape detection (hasOwn checks). In worst case, a malicious or buggy module with matching shape could be incorrectly patched.
  • Recommended action: Tighten the filter to match only actual package entry points: `request === 'qrcode' || request === 'qrcode-terminal' || /\[/\\\\\]qrcode\[/\\\\\]/.test(request) || /\[/\\\\\]qrcode-terminal\[/\\\\\]/.test(request)`. This preserves absolute-path matching for `import('qrcode')` while avoiding false positives.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Check the filter logic at line 147 in whatsapp-qr-compact.ts. Verify unit tests cover the Module._load hook path with absolute paths like `/tmp/app/node_modules/qrcode/lib/index.js`.
  • Missing regression test: Add a test case in whatsapp-qr-compact.test.ts where a module request like 'my-qrcode-wrapper' or 'qrcode-generator' reaches resolvePatchedModule and confirm it is NOT patched (shape detection fails and original module is returned).
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Check the filter logic at line 147 in whatsapp-qr-compact.ts. Verify unit tests cover the Module._load hook path with absolute paths like `/tmp/app/node_modules/qrcode/lib/index.js`.
  • Evidence: Line 147 in whatsapp-qr-compact.ts: `if (typeof request === "string" && request.indexOf("qrcode") !== -1) {`

PRA-5 Resolve/justify — 9 overlapping PRs on nemoclaw-start.sh require merge verification

PRA-6 Improvement — Guard deliberately only flags it(...) primitive, not bare test(...) unit cases

  • Location: scripts/checks/no-unit-blocks-in-live-e2e.ts:38
  • Category: architecture
  • Problem: The guard deliberately only flags `it(...)` primitive, not bare `test(...)` unit cases, because a live test using module-level helpers reads identically to a unit case. This is documented as a known limitation with zero false positives on `it(`. The design is intentional and reasonable.
  • Impact: Pure unit blocks using `test(...)` could still hide in live files, but the `it(...)` ban catches the observed regression pattern with zero false positives.
  • Suggested action: Accept the current design but document the known limitation in the guard's header comment (already present). Consider a follow-up to add a heuristic for `test(` blocks that have no async sandbox fixtures (e.g., no `{ host }`, `{ sandbox }`, `{ artifacts }` parameters), but only if false positives are manageable.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Review the guard's header comment (lines 1-35) which explains the design rationale. Confirm the test file test/no-unit-blocks-in-live-e2e.test.ts verifies `test(...)` is not flagged.
  • Missing regression test: None needed - this is an accepted design trade-off.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Guard header comment lines 1-35 explain the design. Test file verifies test(...) is not flagged.

PRA-7 Improvement — Registry mock pattern adds scaffold overhead; pure function extraction would simplify

  • Location: test/credential-rotation.test.ts:273
  • Category: architecture
  • Problem: The new 'selective-rebuild provider naming' describe block (lines 273-380) still uses the registry mock pattern (vi.spyOn(registry, 'getSandbox')). The core credential rotation detection logic remains coupled to the registry. Extracting a pure function that takes plan entries and tokenDefs as arguments would simplify the 11 tests and remove the registry dependency.
  • Impact: Tests are harder to read and maintain; registry coupling makes it difficult to verify pure logic in isolation.
  • Suggested action: Extract the core credential rotation detection logic into a pure function `detectRotationPure(planEntries, tokenDefs)` that returns { changed, changedProviders }. Simplify tests to call the pure function directly. If registry provides necessary context (e.g., caching), justify why the mock is needed.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check test/credential-rotation.test.ts lines 273-380 - all 6 new tests use vi.spyOn(registry, 'getSandbox').
  • Missing regression test: The new tests already cover the provider naming behavior. After extraction, the same tests would call the pure function directly.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: 6 new tests in credential-rotation.test.ts all use vi.spyOn(registry, 'getSandbox') mock pattern.

PRA-8 Improvement — Monolith growth - extract dcode probe helpers to shared module

  • Location: src/lib/actions/sandbox/snapshot.test.ts:994
  • Category: architecture
  • Problem: Monolith grew by 13 lines (981 → 994). The dcode probe helpers (runProbeScriptWithProcesses, capturedDcodeProbeScript, process line fixtures) are still inline in snapshot.test.ts rather than extracted to a shared module as previously suggested.
  • Impact: Large test file is harder to maintain; dcode probe logic duplicated if needed elsewhere.
  • Suggested action: Extract runProbeScriptWithProcesses, the probe script generator (capturedDcodeProbeScript), and the process line fixtures to a shared helper module (e.g., src/lib/actions/sandbox/snapshot-dcode-probe.ts or test/support/snapshot-dcode-probe.ts). Import in snapshot.test.ts.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Search for runProbeScriptWithProcesses and capturedDcodeProbeScript in snapshot.test.ts - they are defined inline and used only in this file.
  • Missing regression test: None - extraction is a refactoring; existing tests cover the behavior.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: snapshot.test.ts is 994 lines after change. dcode probe helpers defined inline.

PRA-9 Improvement — Guard script lacks integration test for collectLiveUnitBlocks directory walk

  • Location: test/no-unit-blocks-in-live-e2e.test.ts:1
  • Category: tests
  • Problem: The guard script's collectLiveUnitBlocks function walks the test/e2e/live directory but has no integration test that creates a temp directory with violating/non-violating files and asserts correct violations are reported. The existing tests only unit-test findLiveUnitBlocks with string inputs.
  • Impact: Directory traversal logic (walkFiles, file pattern matching) is untested against real filesystem.
  • Suggested action: Add an integration test in test/no-unit-blocks-in-live-e2e.test.ts that creates a temp directory with a mix of violating and non-violating test files, runs collectLiveUnitBlocks, and asserts the correct violations are reported.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check test/no-unit-blocks-in-live-e2e.test.ts - all tests call findLiveUnitBlocks directly with string sources. No test exercises collectLiveUnitBlocks with a real filesystem.
  • Missing regression test: Add integration test for collectLiveUnitBlocks directory walk with real temp files.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Test file only tests findLiveUnitBlocks with string inputs; collectLiveUnitBlocks is untested with real fs.

PRA-10 Improvement — Missing test for Module._load hook false-positive avoidance

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts:163
  • Category: tests
  • Problem: The test file has a positive test for absolute path patching (line 143-163) but lacks a negative test for false-positive avoidance. Modules like 'my-qrcode-wrapper', 'qrcode-generator', '@scope/qrcode-utils' should reach resolvePatchedModule but NOT be patched because shape detection fails.
  • Impact: False-positive patching of unrelated modules would not be caught by tests.
  • Suggested action: Add test case: `it('does NOT patch unrelated module with qrcode in specifier', () => { const mod = { some: 'module' }; const result = resolvePatchedModule('my-qrcode-wrapper', mod); expect(result).toBe(mod); })` and similar for 'qrcode-generator', '@scope/qrcode-utils'.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check whatsapp-qr-compact.test.ts - the 'Module._load hook path-segment matching' describe block only has one positive test. No negative tests for false-positive avoidance.
  • Missing regression test: Add negative test cases for false-positive avoidance in the Module._load hook.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Only one test in 'Module._load hook path-segment matching' describe block; tests absolute path patching but not false-positive avoidance.

PRA-11 Improvement — Dashboard bind validation missing IPv6 address test cases

  • Location: src/lib/onboard/dashboard-access.test.ts:155
  • Category: tests
  • Problem: The new 'NEMOCLAW_DASHBOARD_BIND remote-bind opt-in gate' describe block (lines 79-155) tests various invalid values but does not include IPv6 address test cases (e.g., '::', '::1', '2001:db8::1'). IPv6 bind values should be explicitly rejected to stay loopback.
  • Impact: IPv6 bind values could accidentally open remote bind if not explicitly rejected.
  • Suggested action: Add test case: `it.each(['::', '::1', '2001:db8::1'])('rejects IPv6 bind value %j', (value) => { ... })` to the negative value matrix.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check dashboard-access.test.ts lines 115-135 - the it.each matrix includes '0.0.0.0; rm -rf', '1.2.3.4', 'true', '10.0.0.5', ' 0.0.0.0', '0.0.0.0 ' but no IPv6 addresses.
  • Missing regression test: Add IPv6 bind value rejection test cases to the negative matrix.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: it.each matrix at lines 115-135 has 6 invalid values, none are IPv6 addresses.

PRA-12 Resolve/justify — Fragile CommonJS cache mutation for test mocking

  • Location: src/lib/sandbox/config-get.test.ts:23
  • Category: security
  • Problem: New test file config-get.test.ts uses `require()` to load TypeScript modules and mock `captureOpenshellCommand` by mutating `require.cache`. This works but relies on CommonJS cache mutation which can be brittle across test runs and Vitest's module isolation. The test stubs the openshell client before requiring the config module.
  • Impact: Test flakiness risk; mocks may not clean up properly between tests; Vitest's module isolation may not work as expected with manual cache manipulation.
  • Recommended action: Rewrite using Vitest's `vi.hoisted` and `vi.mock` for more reliable module mocking, or at minimum add `vi.resetModules()` in afterEach to ensure clean state between tests.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Check config-get.test.ts lines 23-45 - it uses `require.resolve`, deletes `require.cache`, and mutates `client.captureOpenshellCommand` directly.
  • Missing regression test: The test coverage itself is good - it tests credential redaction and gateway omission. The mocking approach just needs hardening.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Check config-get.test.ts lines 23-45 - it uses `require.resolve`, deletes `require.cache`, and mutates `client.captureOpenshellCommand` directly.
  • Evidence: Lines 23-45: require.resolve, delete require.cache[configModulePath], client.captureOpenshellCommand = ...

PRA-13 Improvement — Silent catch in Module._load hook degrades to unpatched module without test coverage

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:143
  • Category: security
  • Problem: The resolvePatchedModule function wraps shape detection and patching in try/catch and silently returns the unpatched module on any error. While this is a safe degradation, there is no explicit test verifying this failure path behavior.
  • Impact: If patching fails due to unexpected module shape, the hook silently degrades - no test verifies this behavior or ensures it doesn't mask real bugs.
  • Suggested action: Add a test case that forces a patch failure (e.g., by passing a module that throws during shape detection) and verifies the original module is returned unpatched.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check whatsapp-qr-compact.ts line 143: `} catch (_e) { return loaded; }` - no test exercises this catch block.
  • Missing regression test: Add test for patch failure degradation path in whatsapp-qr-compact.test.ts.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Line 143 in whatsapp-qr-compact.ts: catch (_e) { return loaded; } - untested failure path.

PRA-14 Improvement — Test uses require.cache mutation instead of Vitest mocking

  • Location: src/lib/sandbox/config-get.test.ts:65
  • Category: tests
  • Problem: The config-get.test.ts uses `delete require.cache[configModulePath]` and mutates the openshell client's capture method directly. This is a workaround for Vitest's module isolation when testing CommonJS modules. While it works, it's fragile and should be migrated to proper Vitest mocking.
  • Impact: Test reliability risk; cache mutation can leak state between tests.
  • Suggested action: Migrate to `vi.hoisted` + `vi.mock` pattern for reliable mocking. This is a test-only refactor with no behavior change.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Lines 65 and 103 in config-get.test.ts: `delete require.cache[configModulePath]`
  • Missing regression test: Existing tests cover behavior; mocking refactor should not change coverage.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Two occurrences of delete require.cache[configModulePath] in config-get.test.ts

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

node_options_has_require iterated "${tokens[@]}" on an empty array, which
trips `set -u` on bash 3.2 (macOS default) with "unbound variable" — this
made the nemoclaw-start.sh unit harnesses fail locally even though CI
(bash 5.x) was green. Guard the empty case, and apply the codebase's
existing "${arr[@]+...}" idiom (already used for RESPAWN_TIMES/_PRUNED) to
the two other reachable-empty iterations (_dynamic_targets, run_prefix).

Behavior-preserving; lets the shell-unit suite run on stock macOS bash.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
…st it

An audit of the live E2E suite (which does not run on PR CI) found
behavior-critical assertions that were only guarded by live targets, the
same gap that let the #6065 regressions ship. Backfill the high-priority,
security/recovery-class ones as fast mocked units that run on every PR:

- ollama-auth-proxy: Bearer enforcement, no /api/tags bypass (#3338),
  header stripping, non-ASCII auth no-crash (#4820), backend 502.
- config get: credential redaction + gateway-key omission (the nvapi- class).
- device approval policy: scope-upgrade allowlist gate, gateway-env
  stripping, and recover-failed rejection paths (#4462).
- shields audit JSONL: credentials redacted before persistence.
- hermes env secret boundary: value-shape (not key-name) discriminator
  accepts openshell:resolve refs and rejects raw secrets without echoing
  (added to the dedicated hardening suite).
- dashboard bind: NEMOCLAW_DASHBOARD_BIND opt-in incl. negative cases (#3259).
- whatsapp compact QR: package shape-detection + terminal-only small (#4522).

Guard the recurrence: scripts/checks/no-unit-blocks-in-live-e2e.ts bans the
vitest it(...) primitive inside test/e2e/live/** (those blocks never run on
PR CI). Relocate the two existing offenders — the skill-agent and
messaging-compatible-endpoint classifier blocks — into importable
test/e2e/support modules with PR-collected unit tests; the live tests import
them unchanged.

whatsapp-qr-compact.ts: minimal behavior-preserving refactor to export the
pure shape-detection/patch helpers (the preload still auto-installs on
require); tsconfig.runtime-preloads.json excludes the new co-located test
from the shipped preload build.

SKIP=test-cli: the full cli+integration vitest hook trips on pre-existing
macOS bash 3.2 failures in untouched shell-harness suites (select/set -u);
CI runs bash 5.x green. Every new/changed file here was verified green
individually, and the checks registry + budget + gitleaks + typecheck pass.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts (1)

133-169: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Extract the Module._load routing decision into an exported pure function for testability.

Only the shape-detect/patch helpers are exported; the actual request→loaded routing logic inside installWhatsappQrCompactHook (lines 159-166) is neither exported nor independently testable. As a result, the companion test file has to hand-copy this exact if chain to exercise "path-segment matching" behavior instead of calling the real code — see the corresponding comment in whatsapp-qr-compact.test.ts (this is also what triggered the CI codebase-growth guardrail failure on that test file). Extracting the branch as a named export lets tests call the real routing logic directly and removes the duplicated conditionals from the test.

♻️ Suggested extraction
+// Pure routing decision extracted so callers (and tests) can exercise the
+// exact same logic the installed hook uses, without re-implementing it.
+export function resolvePatchedModule(request: unknown, loaded: unknown) {
+  if (typeof request === "string" && request.indexOf("qrcode") !== -1) {
+    try {
+      if (isQrcodePackage(loaded)) return patchQrcode(loaded);
+      if (isQrcodeTerminalPackage(loaded)) return patchQrcodeTerminal(loaded);
+    } catch (_e) {
+      return loaded;
+    }
+  }
+  return loaded;
+}
+
-export { hasOwn, isQrcodePackage, isQrcodeTerminalPackage, patchQrcode, patchQrcodeTerminal };
+export {
+  hasOwn,
+  isQrcodePackage,
+  isQrcodeTerminalPackage,
+  patchQrcode,
+  patchQrcodeTerminal,
+  resolvePatchedModule,
+};
   Module._load = function (request, _parent, _isMain) {
     var loaded = origLoad.apply(this, arguments);
-    // Cheap path filter: only inspect modules whose request mentions qrcode.
-    // `import("qrcode")` arrives here as the resolved absolute path
-    // (…/qrcode/lib/index.js), so match on the path segment too, not just the
-    // bare specifier.
-    if (typeof request === "string" && request.indexOf("qrcode") !== -1) {
-      try {
-        if (isQrcodePackage(loaded)) return patchQrcode(loaded);
-        if (isQrcodeTerminalPackage(loaded)) return patchQrcodeTerminal(loaded);
-      } catch (_e) {
-        return loaded;
-      }
-    }
-    return loaded;
+    return resolvePatchedModule(request, loaded);
   };
🤖 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/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts` around
lines 133 - 169, The Module._load request-to-loaded routing inside
installWhatsappQrCompactHook is duplicated in tests and not independently
testable. Extract that request matching and patch-selection branch into a new
exported pure function, then have installWhatsappQrCompactHook call it from the
Module._load wrapper. Update the companion test to invoke the exported routing
function directly instead of hand-copying the if chain, while keeping
isQrcodePackage, isQrcodeTerminalPackage, patchQrcode, and patchQrcodeTerminal
as the underlying helpers.

Source: Pipeline failures

🧹 Nitpick comments (5)
test/ollama-auth-proxy-handler.test.ts (1)

59-68: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Port-release/rebind window can flake under CI port churn.

freePort() closes the probe socket before startProxy() binds to the same port; another process could grab it in between under parallel CI load, causing an intermittent proxy-start failure or wrong process binding the port.

🤖 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 `@test/ollama-auth-proxy-handler.test.ts` around lines 59 - 68, The current
freePort() helper in the test can race because it closes the probe server before
startProxy() binds, leaving a window where another process may claim the port.
Update the test setup so the proxy binds to the discovered port without a
release/rebind gap, or otherwise keep the probe reserved until the proxy is
ready. Use the freePort() and startProxy() helpers in
test/ollama-auth-proxy-handler.test.ts to locate and adjust the port allocation
flow.
src/lib/shields/audit-format.test.ts (1)

131-144: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same env save/restore-via-if pattern as dashboard-access.test.ts.

This is the identical if (saved === undefined) delete ... else process.env.X = saved pattern that trips the test-conditionals:scan growth guardrail in the sibling dashboard-access.test.ts file in this cohort. Consider switching to vi.stubEnv("HOME", homeDir) / vi.unstubAllEnvs() here too, both to avoid the same conditional-growth risk and to de-duplicate this boilerplate across the two files.

🤖 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/shields/audit-format.test.ts` around lines 131 - 144, The HOME env
setup/restore in the test hooks repeats the same save/restore conditional
pattern that should be removed. Update the `beforeEach`/`afterEach` logic in
`audit-format.test.ts` to use `vi.stubEnv("HOME", homeDir)` and
`vi.unstubAllEnvs()` instead of manually saving `savedHome` and branching on
restore, matching the approach used in `dashboard-access.test.ts` and avoiding
the conditional boilerplate in these hooks.
test/openclaw-device-approval-policy.test.ts (1)

49-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated Python bootstrap boilerplate across three helper functions.

callDecision, callGatewayEnv, and runRecovery (line 26 onward, not shown in this segment) each embed the same importlib.util.spec_from_file_location / module_from_spec / exec_module bootstrap. Consider factoring the common module-load prologue into a shared template string interpolated with the call-specific tail, to avoid drift if the loading mechanism ever 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 `@test/openclaw-device-approval-policy.test.ts` around lines 49 - 90, The
Python bootstrap logic is duplicated across callDecision, callGatewayEnv, and
runRecovery, so factor the shared importlib.util module-loading prologue into
one reusable template or helper string. Keep the call-specific tail separate for
approval_request_decision, gateway_approval_env, and the recovery path, and have
all three helpers build on the same shared loader so future changes to module
loading stay consistent.
test/hermes-env-secret-boundary-hardening.test.ts (1)

76-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New helper duplicates the runtime-boundary harness already in test/hermes-start.test.ts.

runRuntimeEnvValidation is nearly identical to runHermesRuntimeEnvSecretBoundary in test/hermes-start.test.ts (temp dir setup, script generation, _HERMES_BOUNDARY_TIMEOUT no-op, spawnSync options). This PR already needed to apply the same set -u/bash-3.2 fix in both places — a sign the duplication is starting to drift. Consider extracting a shared harness helper (e.g. under test/support/) that both files import, so future fixes only need to land once.

🤖 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 `@test/hermes-env-secret-boundary-hardening.test.ts` around lines 76 - 111, The
runtime-boundary harness logic in runRuntimeEnvValidation is duplicated from
runHermesRuntimeEnvSecretBoundary and will drift again; extract the shared
temp-script/spawnSync setup into a common helper under test/support/ and have
both tests call it. Keep the existing set -u, _HERMES_BOUNDARY_TIMEOUT no-op,
and validator wiring behavior intact when moving the shared logic.
src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts (1)

84-131: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Static-analysis prototype-pollution flag is likely a false positive here.

for...in + hasOwnProperty copies attacker-controllable keys, but opts here is always supplied by internal WhatsApp render call sites (never untrusted external input), and merged is a fresh local object per call — a __proto__ key would only affect that one local object, not the shared Object.prototype. Given the internal-only threat model, a guard is optional defense-in-depth rather than a real fix for an exploitable path.

🛡️ Optional defensive guard (applies to both merge loops, lines 94-96 and 121-123)
     for (var key in opts) {
-      if (Object.prototype.hasOwnProperty.call(opts, key)) merged[key] = opts[key];
+      if (
+        Object.prototype.hasOwnProperty.call(opts, key) &&
+        key !== "__proto__" &&
+        key !== "constructor" &&
+        key !== "prototype"
+      ) {
+        merged[key] = opts[key];
+      }
     }
🤖 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/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts` around
lines 84 - 131, The prototype-pollution warning comes from the option-copy loops
inside patchQrcode and patchQrcodeTerminal, where merged is built from opts via
for...in plus hasOwnProperty. Add a small defensive guard in both merge loops to
skip dangerous keys like __proto__, constructor, and prototype while keeping the
existing internal behavior unchanged, so the local merged object cannot be
poisoned even if opts is ever unexpected.

Source: Linters/SAST tools

🤖 Prompt for all review comments with 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.

Inline comments:
In `@src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts`:
- Around line 141-169: This test is duplicating the production Module._load
routing logic instead of exercising the real behavior, so replace the
hand-copied path-segment and package-shape conditionals in
whatsapp-qr-compact.test.ts with a call to the exported resolvePatchedModule
helper from whatsapp-qr-compact.ts once it is available. Keep the test focused
on asserting that installWhatsappQrCompactHook routes an absolute qrcode path
through the actual production logic and returns the patched module, rather than
re-implementing the branching inside the test.

In `@src/lib/onboard/dashboard-access.test.ts`:
- Around line 90-98: Replace the manual process.env snapshot/restore in
dashboard-access.test.ts with Vitest env stubbing. Update the setup around the
NEMOCLAW_DASHBOARD_BIND cases to use vi.stubEnv for each test value, and remove
the savedEnv if/else cleanup in afterEach in favor of vi.unstubAllEnvs. Keep the
changes localized to the dashboard access test helpers so the existing test
cases continue to use the same NEMOCLAW_DASHBOARD_BIND symbol.

In `@test/hermes-env-secret-boundary-hardening.test.ts`:
- Around line 54-58: The no-op prefix used in the hermes boundary harness is too
GNU-specific because the `env` invocation in the `_HERMES_BOUNDARY_TIMEOUT`
setup uses `--`, which breaks on macOS/BSD before the validator runs. Update the
prefix in `hermes-env-secret-boundary-hardening.test.ts` to use `env` without
`--`, keeping the existing boundary harness behavior intact while making it
portable across platforms.

In `@test/openclaw-device-approval-policy.test.ts`:
- Around line 45-47: The tests gated by hasPython3() are currently returning
early and showing as passed when python3 is missing, which hides skipped
coverage. Update the affected Vitest suite in
openclaw-device-approval-policy.test.ts to use skip semantics such as
describe.skipIf or it.skipIf around the python-dependent cases instead of
returning from each test. Use hasPython3() as the condition, and apply it
consistently to the suite or each affected test so the reports clearly show
skipped tests rather than false positives.

---

Outside diff comments:
In `@src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts`:
- Around line 133-169: The Module._load request-to-loaded routing inside
installWhatsappQrCompactHook is duplicated in tests and not independently
testable. Extract that request matching and patch-selection branch into a new
exported pure function, then have installWhatsappQrCompactHook call it from the
Module._load wrapper. Update the companion test to invoke the exported routing
function directly instead of hand-copying the if chain, while keeping
isQrcodePackage, isQrcodeTerminalPackage, patchQrcode, and patchQrcodeTerminal
as the underlying helpers.

---

Nitpick comments:
In `@src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts`:
- Around line 84-131: The prototype-pollution warning comes from the option-copy
loops inside patchQrcode and patchQrcodeTerminal, where merged is built from
opts via for...in plus hasOwnProperty. Add a small defensive guard in both merge
loops to skip dangerous keys like __proto__, constructor, and prototype while
keeping the existing internal behavior unchanged, so the local merged object
cannot be poisoned even if opts is ever unexpected.

In `@src/lib/shields/audit-format.test.ts`:
- Around line 131-144: The HOME env setup/restore in the test hooks repeats the
same save/restore conditional pattern that should be removed. Update the
`beforeEach`/`afterEach` logic in `audit-format.test.ts` to use
`vi.stubEnv("HOME", homeDir)` and `vi.unstubAllEnvs()` instead of manually
saving `savedHome` and branching on restore, matching the approach used in
`dashboard-access.test.ts` and avoiding the conditional boilerplate in these
hooks.

In `@test/hermes-env-secret-boundary-hardening.test.ts`:
- Around line 76-111: The runtime-boundary harness logic in
runRuntimeEnvValidation is duplicated from runHermesRuntimeEnvSecretBoundary and
will drift again; extract the shared temp-script/spawnSync setup into a common
helper under test/support/ and have both tests call it. Keep the existing set
-u, _HERMES_BOUNDARY_TIMEOUT no-op, and validator wiring behavior intact when
moving the shared logic.

In `@test/ollama-auth-proxy-handler.test.ts`:
- Around line 59-68: The current freePort() helper in the test can race because
it closes the probe server before startProxy() binds, leaving a window where
another process may claim the port. Update the test setup so the proxy binds to
the discovered port without a release/rebind gap, or otherwise keep the probe
reserved until the proxy is ready. Use the freePort() and startProxy() helpers
in test/ollama-auth-proxy-handler.test.ts to locate and adjust the port
allocation flow.

In `@test/openclaw-device-approval-policy.test.ts`:
- Around line 49-90: The Python bootstrap logic is duplicated across
callDecision, callGatewayEnv, and runRecovery, so factor the shared
importlib.util module-loading prologue into one reusable template or helper
string. Keep the call-specific tail separate for approval_request_decision,
gateway_approval_env, and the recovery path, and have all three helpers build on
the same shared loader so future changes to module loading stay consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4a2394ba-fc26-43d5-8633-4a9daadcdf66

📥 Commits

Reviewing files that changed from the base of the PR and between b0ab758 and 7976da1.

📒 Files selected for processing (20)
  • scripts/checks/no-unit-blocks-in-live-e2e.ts
  • scripts/checks/run.ts
  • scripts/nemoclaw-start.sh
  • src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts
  • src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts
  • src/lib/onboard/dashboard-access.test.ts
  • src/lib/sandbox/config-get.test.ts
  • src/lib/shields/audit-format.test.ts
  • test/e2e/live/messaging-compatible-endpoint.test.ts
  • test/e2e/live/skill-agent.test.ts
  • test/e2e/support/messaging-endpoint-classifiers.test.ts
  • test/e2e/support/messaging-endpoint-classifiers.ts
  • test/e2e/support/skill-agent-classifiers.test.ts
  • test/e2e/support/skill-agent-classifiers.ts
  • test/hermes-env-secret-boundary-hardening.test.ts
  • test/hermes-start.test.ts
  • test/no-unit-blocks-in-live-e2e.test.ts
  • test/ollama-auth-proxy-handler.test.ts
  • test/openclaw-device-approval-policy.test.ts
  • tsconfig.runtime-preloads.json
✅ Files skipped from review due to trivial changes (3)
  • test/no-unit-blocks-in-live-e2e.test.ts
  • test/e2e/support/messaging-endpoint-classifiers.ts
  • scripts/checks/no-unit-blocks-in-live-e2e.ts

Comment on lines +90 to +98
const savedEnv = process.env.NEMOCLAW_DASHBOARD_BIND;

afterEach(() => {
if (savedEnv === undefined) {
delete process.env.NEMOCLAW_DASHBOARD_BIND;
} else {
process.env.NEMOCLAW_DASHBOARD_BIND = savedEnv;
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== package vitest version ==\n'
if [ -f package.json ]; then
  python3 - <<'PY'
import json
from pathlib import Path
p = Path("package.json")
data = json.loads(p.read_text())
for section in ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies"):
    deps = data.get(section, {})
    if "vitest" in deps:
        print(section, deps["vitest"])
PY
fi

printf '\n== lockfile vitest references ==\n'
rg -n '"vitest"|vitest@' package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null | head -n 80 || true

printf '\n== dashboard-access.test.ts excerpt ==\n'
sed -n '1,220p' src/lib/onboard/dashboard-access.test.ts

Repository: NVIDIA/NemoClaw

Length of output: 6238


🌐 Web query:

Vitest 4.1.0 vi.stubEnv vi.unstubAllEnvs API

💡 Result:

In Vitest, vi.stubEnv and vi.unstubAllEnvs are utilities designed to safely modify and manage environment variables within your tests [1][2]. vi.stubEnv(name, value) This function changes the value of an environment variable on both process.env and import.meta.env [1][3]. - It tracks the original value of the variable, allowing you to restore it later [1][2]. - You should use this method instead of direct assignment if you intend to use vi.unstubAllEnvs to reset the state, as direct assignment will not be tracked by Vitest [3]. vi.unstubAllEnvs This function restores all environment variables changed via vi.stubEnv to their original values [1][3]. - When called for the first time, Vitest records the original values of the modified variables and maintains them until this function is called again [1][2]. Automatic Reset You can configure Vitest to automatically call vi.unstubAllEnvs before every test by enabling the unstubEnvs option in your Vitest configuration file [4][5]: // vitest.config.ts export default defineConfig({ test: { unstubEnvs: true, }, }) Warning: Enabling unstubEnvs may cause issues with concurrent, asynchronous tests, as one test finishing and resetting variables could impact other tests running simultaneously [5]. Alternatively, you can manually call vi.unstubAllEnvs in a beforeEach hook [4][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== vitest API docs in repo references ==\n'
rg -n "stubEnv|unstubAllEnvs|restoreAllMocks|unstubAllEnvs" src test . 2>/dev/null | head -n 80 || true

Repository: NVIDIA/NemoClaw

Length of output: 6122


Replace the manual env restore with Vitest env stubs in src/lib/onboard/dashboard-access.test.ts:90-146
vi.stubEnv/vi.unstubAllEnvs are available in Vitest 4.1.0, so the if/else snapshot can go. Update the two process.env.NEMOCLAW_DASHBOARD_BIND cases to vi.stubEnv(...) as well, since direct assignment won’t be cleaned up by unstubAllEnvs.

🤖 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/dashboard-access.test.ts` around lines 90 - 98, Replace the
manual process.env snapshot/restore in dashboard-access.test.ts with Vitest env
stubbing. Update the setup around the NEMOCLAW_DASHBOARD_BIND cases to use
vi.stubEnv for each test value, and remove the savedEnv if/else cleanup in
afterEach in favor of vi.unstubAllEnvs. Keep the changes localized to the
dashboard access test helpers so the existing test cases continue to use the
same NEMOCLAW_DASHBOARD_BIND symbol.

Source: Pipeline failures

Comment on lines +54 to +58
// A harmless no-op prefix (not an empty array): macOS bash 3.2 treats
// "${empty[@]}" as an unbound variable under `set -u`, which would abort
// the harness before the validator ever runs. `env --` just execs the
// validator unchanged.
"_HERMES_BOUNDARY_TIMEOUT=(env --)",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does the macOS/BSD env(1) command support "--" to signal end of options before the command to run?

💡 Result:

No, the macOS env(1) command does not support the "--" argument to signal the end of options [1][2]. The macOS implementation of env (which is derived from FreeBSD [3]) follows its own specific syntax [2] and does not adhere to the common POSIX utility convention of using "--" to terminate option parsing [4][5]. According to the macOS manual page [2], options must be specified before any name=value pairs and the utility to be executed. If you attempt to use "--", env will typically treat it as an invalid option or as part of the arguments rather than a delimiter [2]. This behavior differs from GNU env (found on most Linux distributions), which does support standard POSIX-style argument parsing [6][7]. If you require a way to safely pass arguments that might look like options to a command executed via env on macOS, you may need to use a wrapper, a shell function, or sh -c to invoke the target command instead.

Citations:


🏁 Script executed:

sed -n '1,140p' test/hermes-env-secret-boundary-hardening.test.ts

Repository: NVIDIA/NemoClaw

Length of output: 5177


🏁 Script executed:

rg -n "_HERMES_BOUNDARY_TIMEOUT|validate_hermes_env_secret_boundary|validate_hermes_runtime_env_secret_boundary" agents/hermes/start.sh test/hermes-env-secret-boundary-hardening.test.ts

Repository: NVIDIA/NemoClaw

Length of output: 2101


🏁 Script executed:

sed -n '1704,1720p' agents/hermes/start.sh

Repository: NVIDIA/NemoClaw

Length of output: 610


Drop the -- from this env prefix
env -- is not supported by macOS/BSD env, so this harness will fail on the target platform before the validator runs. env alone keeps the prefix portable.

🤖 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 `@test/hermes-env-secret-boundary-hardening.test.ts` around lines 54 - 58, The
no-op prefix used in the hermes boundary harness is too GNU-specific because the
`env` invocation in the `_HERMES_BOUNDARY_TIMEOUT` setup uses `--`, which breaks
on macOS/BSD before the validator runs. Update the prefix in
`hermes-env-secret-boundary-hardening.test.ts` to use `env` without `--`,
keeping the existing boundary harness behavior intact while making it portable
across platforms.

Comment thread test/openclaw-device-approval-policy.test.ts Outdated
Second batch from the live-E2E coverage audit — the medium/low-priority
seams, as fast mocked units that run on PR CI:

- ollama-auth-proxy token file lifecycle: 0600 mode, persisted-token match,
  and divergent-token repair on restart (host runner).
- extra-placeholder-keys: distinct accepted keys map to distinct canonical
  openshell:resolve:env: placeholders, and the accepted-keys breadcrumb names
  accepted keys while omitting a co-submitted refused GITHUB_TOKEN.
- hermes remove_stale_gateway_file: a symlink or stale file at the gateway
  PID path is removed without following the link (regular file, never symlink).
- token-rotation: selective-rebuild names only the changed provider(s).
- _validate_port: out-of-range/non-numeric ports fail closed with the exact
  "Invalid <NAME>=<value> (expected 1024-65535)" message.
- snapshot: the bare `snapshot` help branch prints create/list/restore usage.

Also relocate the pure-unit cases that lived as bare test(...) inside two live
files (common-egress parsers + openclaw-inference-switch reply matcher) into
importable test/e2e/support helper modules with PR-collected unit tests; the
live tests import the helpers unchanged.

Two audit items are intentionally deferred: the install.sh "Resolved install
ref:" log assertion and the OpenClaw anthropic plain-baseUrl assertion both
land in legacy-budget-capped files where the growth guardrail forbids bumping
the budget; both are low value (a log string; a contract already covered on
the Hermes side and enforced live on the OpenClaw side).

SKIP=test-cli: the full cli+integration vitest hook trips on pre-existing
macOS bash 3.2 failures in untouched shell-harness suites; CI runs bash 5.x
green. Every new/changed file here was verified green individually, and the
checks registry + budget + gitleaks + CLI typecheck pass.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
@prekshivyas prekshivyas changed the title test(start): pin recovery + override regressions as mocked shell-units test: backfill mockable coverage for live-only behavior + guard against it Jul 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (8)
test/nemoclaw-start-extra-placeholder-breadcrumb.test.ts (2)

64-99: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Temp directory not cleaned up if an assertion or parse throws before line 97.

fs.rmSync(root, ...) at line 97 only runs if JSON.parse on line 96 succeeds; if spawnSync result is unexpected or the config file is corrupted, the temp dir under os.tmpdir() leaks. Low risk in CI but worth wrapping in try/finally for hygiene.

♻️ Suggested cleanup guarantee
-    const updated = JSON.parse(fs.readFileSync(configPath, "utf-8"));
-    fs.rmSync(root, { recursive: true, force: true });
-    return { result, config: updated };
+    try {
+      const updated = JSON.parse(fs.readFileSync(configPath, "utf-8"));
+      return { result, config: updated };
+    } finally {
+      fs.rmSync(root, { recursive: true, force: 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 `@test/nemoclaw-start-extra-placeholder-breadcrumb.test.ts` around lines 64 -
99, The temporary directory cleanup in runRefresh is not guaranteed because
fs.rmSync(root, ...) only executes after JSON.parse and other assertions
succeed. Wrap the body of runRefresh around the temp-dir setup, spawnSync
execution, and config read in a try/finally so the root directory is always
removed even if parsing or an assertion fails.

38-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Heredoc terminator matching doesn't strip leading whitespace for <<- style heredocs.

The opener regex accepts <<-? (both << and <<- forms), but the terminator-line check line === heredocTerminator requires an exact match. With <<-'TERM', bash permits the closing terminator line to be tab-indented, which this check would miss, causing the loop to run to EOF and throw "Expected a top-level close...". Not currently triggered (the script apparently only uses <<'PY'), but worth hardening or documenting the assumption since this is a shared test helper.

🔧 Suggested hardening
       if (heredocTerminator !== null) {
-        if (line === heredocTerminator) heredocTerminator = null;
+        if (line.trimStart() === heredocTerminator) heredocTerminator = null;
         continue;
       }
🤖 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 `@test/nemoclaw-start-extra-placeholder-breadcrumb.test.ts` around lines 38 -
57, The heredoc parsing in extractShellFunction currently treats all terminators
as exact string matches, which breaks `<<-` heredocs where the closing token may
be tab-indented. Update the helper to distinguish `<<-` from `<<` when setting
heredocTerminator, and allow leading tab whitespace when matching the closing
line for `<<-` forms while keeping exact matching for normal heredocs. Use the
existing `extractShellFunction` logic and its opener regex as the place to
harden this behavior.
test/runtime-shell.test.ts (1)

168-174: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate it.each dataset across both blocks.

The same six { name, value } cases are repeated verbatim for get_local_provider_base_url and check_local_provider_health. Extracting into a shared const invalidPortCases = [...] would remove the duplication.

Also applies to: 186-192

🤖 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 `@test/runtime-shell.test.ts` around lines 168 - 174, The `it.each` invalid
port cases are duplicated in both the `get_local_provider_base_url` and
`check_local_provider_health` test blocks. Extract the repeated `{ name, value
}` dataset into a shared `const invalidPortCases` near the top of the test file
and reuse it in both `it.each` calls to keep the cases defined once.
test/credential-rotation.test.ts (2)

260-379: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider consolidating near-duplicate cases with it.each.

The five tests share identical structure (build a threeProviderPlan, call detectMessagingCredentialRotation, assert changedProviders), differing only in which hashes rotate. A table-driven it.each would cut duplication while preserving the same behavioral coverage.

🤖 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 `@test/credential-rotation.test.ts` around lines 260 - 379, The five
`detectMessagingCredentialRotation` tests are near-duplicates and should be
consolidated into a table-driven `it.each` block. Keep the shared setup around
`threeProviderPlan`, `registry.getSandbox`, and the `changedProviders`
assertions, and parameterize only the differing hash/token scenarios plus the
expected provider list and `changed` value. Preserve the existing coverage for
A-only, middle-only, multiple, all, and none cases while reducing repetition in
`test/credential-rotation.test.ts`.

272-379: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Move vi.restoreAllMocks() into afterEach for guaranteed cleanup.

Each test calls vi.restoreAllMocks() as its last statement. If an expect(...) earlier in the test throws, that line never runs and the registry.getSandbox spy leaks into subsequent tests, risking cross-test pollution.

🔧 Proposed fix
+  afterEach(() => {
+    vi.restoreAllMocks();
+  });
+
   describe("selective-rebuild provider naming", () => {
     ...
     it("names ONLY provider A and excludes unchanged siblings B and C", () => {
       ...
       expect(result.changedProviders.join(", ")).toBe(A);
-      vi.restoreAllMocks();
     });

(repeat removal for the other four it blocks)

🤖 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 `@test/credential-rotation.test.ts` around lines 272 - 379, Move the repeated
vi.restoreAllMocks() cleanup out of each detectMessagingCredentialRotation test
and into a shared afterEach so it always runs even if an assertion fails. Update
the surrounding test suite that spies on registry.getSandbox to rely on this
global teardown instead of per-test cleanup, and remove the trailing restore
call from each it block.
test/hermes-gateway-pid-cleanup.test.ts (1)

58-132: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the repeated try/finally cleanup.

Each of the four it blocks repeats the same try { ... } finally { fs.rmSync(tmp, { recursive: true, force: true }); } pattern. A small helper (e.g. withTmpCleanup(tmp, fn)) or a afterEach hook tracking the last created tmp dir would remove this duplication and reduce the chance of a future test forgetting the cleanup.

♻️ Example refactor sketch
+let currentTmp: string | undefined;
+
+afterEach(() => {
+  if (currentTmp) {
+    fs.rmSync(currentTmp, { recursive: true, force: true });
+    currentTmp = undefined;
+  }
+});
+
 function runRemoveStale(
   seed: (tmp: string, pidPath: string) => void,
   label = "legacy PID file",
 ): { status: number | null; stderr: string; tmp: string; pidPath: string } {
   ...
   const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "hermes-gw-pid-cleanup-"));
+  currentTmp = tmp;
   ...
 }
🤖 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 `@test/hermes-gateway-pid-cleanup.test.ts` around lines 58 - 132, The four
tests in hermes-gateway-pid-cleanup.test.ts repeat the same tmp cleanup
try/finally pattern. Extract that cleanup into a shared helper or an
afterEach-based cleanup tied to runRemoveStale so each it block only focuses on
assertions, and the tmp directory is always removed consistently.
test/ollama-proxy-recovery.test.ts (2)

622-624: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid pinning the repair test to the exact kill invocation.

Line 623 asserts one specific shell command and call order instead of the recovery behavior. This will fail on harmless cleanup refactors (process.kill, helper indirection, extra preflight commands) even if the proxy still repairs correctly. As per path instructions, "Prefer observable outcomes through the public boundary over source-text, private-shape, or mock-call assertions."

🤖 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 `@test/ollama-proxy-recovery.test.ts` around lines 622 - 624, The repair test
is too tightly coupled to the exact `kill` command and mock call order in the
recovery flow. Update the assertions in `ollama-proxy-recovery.test.ts` to
verify the observable recovery outcome through the public boundary instead of
`payload.runCommands[0]`, while still checking the stale proxy is reclaimed and
`spawnedToken` is the FILE token via the recovery scenario helpers.

Source: Path instructions


395-398: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Loosen the cleanup assertion in the divergent-token test. The test already proves the repair path by checking the spawned token and on-disk token; asserting runCommands[0] is exactly ["kill", "4242"] locks it to one cleanup implementation and adds avoidable churn on refactors.

🤖 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 `@test/ollama-proxy-recovery.test.ts` around lines 395 - 398, Loosen the
cleanup assertion in the divergent-token test so it validates the repair
behavior without requiring a specific cleanup command shape. In the test around
the proxy recovery flow, keep the checks that confirm the spawned token and
on-disk token match, but replace the exact runCommands[0] equality assertion
with a more flexible assertion tied to the cleanup path in the recovery logic so
refactors in the cleanup implementation do not break the test.

Source: Learnings

🤖 Prompt for all review comments with 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.

Inline comments:
In `@test/nemoclaw-start-extra-placeholder-breadcrumb.test.ts`:
- Around line 1-198: The new conditionals in extractShellFunction are tripping
the Codebase Growth Guardrails count for this test file. Simplify the
heredoc-aware parsing logic in the
test/nemoclaw-start-extra-placeholder-breadcrumb suite to avoid adding counted
if statements, or move the helper logic out of the *.test.ts file into shared
non-test support so the guardrail no longer sees it. Keep the existing behavior
of extractShellFunction and runRefresh intact while reducing the conditional
footprint.

---

Nitpick comments:
In `@test/credential-rotation.test.ts`:
- Around line 260-379: The five `detectMessagingCredentialRotation` tests are
near-duplicates and should be consolidated into a table-driven `it.each` block.
Keep the shared setup around `threeProviderPlan`, `registry.getSandbox`, and the
`changedProviders` assertions, and parameterize only the differing hash/token
scenarios plus the expected provider list and `changed` value. Preserve the
existing coverage for A-only, middle-only, multiple, all, and none cases while
reducing repetition in `test/credential-rotation.test.ts`.
- Around line 272-379: Move the repeated vi.restoreAllMocks() cleanup out of
each detectMessagingCredentialRotation test and into a shared afterEach so it
always runs even if an assertion fails. Update the surrounding test suite that
spies on registry.getSandbox to rely on this global teardown instead of per-test
cleanup, and remove the trailing restore call from each it block.

In `@test/hermes-gateway-pid-cleanup.test.ts`:
- Around line 58-132: The four tests in hermes-gateway-pid-cleanup.test.ts
repeat the same tmp cleanup try/finally pattern. Extract that cleanup into a
shared helper or an afterEach-based cleanup tied to runRemoveStale so each it
block only focuses on assertions, and the tmp directory is always removed
consistently.

In `@test/nemoclaw-start-extra-placeholder-breadcrumb.test.ts`:
- Around line 64-99: The temporary directory cleanup in runRefresh is not
guaranteed because fs.rmSync(root, ...) only executes after JSON.parse and other
assertions succeed. Wrap the body of runRefresh around the temp-dir setup,
spawnSync execution, and config read in a try/finally so the root directory is
always removed even if parsing or an assertion fails.
- Around line 38-57: The heredoc parsing in extractShellFunction currently
treats all terminators as exact string matches, which breaks `<<-` heredocs
where the closing token may be tab-indented. Update the helper to distinguish
`<<-` from `<<` when setting heredocTerminator, and allow leading tab whitespace
when matching the closing line for `<<-` forms while keeping exact matching for
normal heredocs. Use the existing `extractShellFunction` logic and its opener
regex as the place to harden this behavior.

In `@test/ollama-proxy-recovery.test.ts`:
- Around line 622-624: The repair test is too tightly coupled to the exact
`kill` command and mock call order in the recovery flow. Update the assertions
in `ollama-proxy-recovery.test.ts` to verify the observable recovery outcome
through the public boundary instead of `payload.runCommands[0]`, while still
checking the stale proxy is reclaimed and `spawnedToken` is the FILE token via
the recovery scenario helpers.
- Around line 395-398: Loosen the cleanup assertion in the divergent-token test
so it validates the repair behavior without requiring a specific cleanup command
shape. In the test around the proxy recovery flow, keep the checks that confirm
the spawned token and on-disk token match, but replace the exact runCommands[0]
equality assertion with a more flexible assertion tied to the cleanup path in
the recovery logic so refactors in the cleanup implementation do not break the
test.

In `@test/runtime-shell.test.ts`:
- Around line 168-174: The `it.each` invalid port cases are duplicated in both
the `get_local_provider_base_url` and `check_local_provider_health` test blocks.
Extract the repeated `{ name, value }` dataset into a shared `const
invalidPortCases` near the top of the test file and reuse it in both `it.each`
calls to keep the cases defined once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 36212af4-bb80-4d15-9a7f-6098a0736154

📥 Commits

Reviewing files that changed from the base of the PR and between 7976da1 and a35e24a.

📒 Files selected for processing (12)
  • src/lib/actions/sandbox/snapshot.test.ts
  • test/credential-rotation.test.ts
  • test/e2e/live/common-egress-agent-helpers.ts
  • test/e2e/live/common-egress-agent.test.ts
  • test/e2e/live/openclaw-inference-switch-helpers.ts
  • test/e2e/live/openclaw-inference-switch.test.ts
  • test/e2e/support/common-egress-agent-helpers.test.ts
  • test/e2e/support/openclaw-inference-switch-helpers.test.ts
  • test/hermes-gateway-pid-cleanup.test.ts
  • test/nemoclaw-start-extra-placeholder-breadcrumb.test.ts
  • test/ollama-proxy-recovery.test.ts
  • test/runtime-shell.test.ts
✅ Files skipped from review due to trivial changes (1)
  • test/e2e/support/common-egress-agent-helpers.test.ts

Comment thread test/nemoclaw-start-extra-placeholder-breadcrumb.test.ts
prekshivyas and others added 3 commits July 1, 2026 12:01
The codebase-growth-guardrails check requires changed *.test.ts files not to
add `if` statements — test bodies must stay linear. Move the if-bearing
harness/stub code (ollama HTTP stub + driver, device-approval python invokers,
hermes/placeholder shell-fn extractors, whatsapp fake-module builder) out of
the counted test files into co-located non-test helper modules, and replace
the device-approval per-test `if (!hasPython3()) return;` gates with a
module-level it.skipIf. Env-restore teardown branches become branchless
Object.assign. No test behavior changes; every affected suite still passes.

tsconfig.runtime-preloads.json also excludes *-test-helpers.ts so the new
whatsapp test helper is not compiled into the shipped runtime preloads.

SKIP=test-cli: same pre-existing macOS bash 3.2 shell-harness failures as the
prior commits; CI runs bash 5.x green. All touched suites verified green
individually; checks registry + budget + typecheck pass.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
…hell-units' into test/mock-recovery-reconcile-shell-units

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
test/hermes-gateway-pid-cleanup.test.ts (1)

17-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Solid observable-outcome coverage for the cleanup contract.

Each test asserts on exit status, stderr text, and real filesystem state (symlink/file existence) rather than internal implementation details, which aligns well with behavioral-confidence testing for this contract.

One minor, purely optional improvement: the try { ... } finally { fs.rmSync(tmp, { recursive: true, force: true }); } cleanup boilerplate is duplicated across all four tests. Could be consolidated with an afterEach that tracks the current tmp dir, but this is a nitpick and not required.

🤖 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 `@test/hermes-gateway-pid-cleanup.test.ts` around lines 17 - 92, The tests
already cover the cleanup behavior well, but the repeated try/finally tmp
directory teardown is duplicated across all four cases. Refactor the `Hermes
remove_stale_gateway_file cleanup (legacy gateway.pid)` suite to centralize the
temporary directory cleanup, ideally via a shared `afterEach` or helper used by
`runRemoveStale`-based tests, while keeping the existing assertions on `stderr`,
`status`, and filesystem state unchanged.
test/ollama-auth-proxy-handler-helpers.ts (1)

121-150: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

request() has no timeout; a hung proxy hangs the test indefinitely.

Every request relies solely on Vitest's own test timeout as a backstop. A short request timeout here would fail faster with a clearer error message instead of surfacing as a generic suite timeout.

🤖 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 `@test/ollama-auth-proxy-handler-helpers.ts` around lines 121 - 150, The
request helper in request() lacks an explicit network timeout, so a stalled
proxy can leave tests hanging until the suite timeout. Add a short timeout to
the http.request call in request(), and make sure the timeout handler aborts the
request and rejects with a clear error so failures surface quickly and clearly.
Keep the change localized to the request() helper and preserve the existing
resolve/reject behavior for successful responses and other request errors.
🤖 Prompt for all review comments with 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.

Inline comments:
In
`@src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact-test-helpers.ts`:
- Around line 24-33: The non-qrcode branch is still triggering the qrcode
patching side effects because `patchQrcode` and `patchQrcodeTerminal` are
evaluated before `isQrcodeRequest` is checked. Update the module loader function
in `whatsapp-qr-compact-test-helpers` so the `isQrcodeRequest` guard wraps the
patching logic itself, and only compute `patched` when the request string
actually contains "qrcode"; otherwise return the unmodified `loaded` value.

In `@test/nemoclaw-start-extra-placeholder-breadcrumb-helpers.ts`:
- Around line 49-85: The temp directory cleanup in runRefresh should always run
even when spawnSync fails or the config file cannot be parsed. Move the
fs.rmSync(root, ...) cleanup into a finally-style path around the
JSON.parse/readback logic, so root is removed regardless of failures. Use
runRefresh, spawnSync, and the config read/parse block as the anchor points when
updating the helper.

In `@test/ollama-auth-proxy-handler-helpers.ts`:
- Around line 80-96: The retry loop in startProxy keeps rescheduling tryConnect
after the promise has already settled, so add a cancellation/settled flag in the
helper around the http.request retry logic. Update the startProxy Promise flow
to set that flag on resolve, reject, and the child.once("exit") path, and guard
both the req.on("error") retry scheduling and the success callback so no further
reconnect attempts or late resolve/clearTimeout calls happen once settled.

---

Nitpick comments:
In `@test/hermes-gateway-pid-cleanup.test.ts`:
- Around line 17-92: The tests already cover the cleanup behavior well, but the
repeated try/finally tmp directory teardown is duplicated across all four cases.
Refactor the `Hermes remove_stale_gateway_file cleanup (legacy gateway.pid)`
suite to centralize the temporary directory cleanup, ideally via a shared
`afterEach` or helper used by `runRemoveStale`-based tests, while keeping the
existing assertions on `stderr`, `status`, and filesystem state unchanged.

In `@test/ollama-auth-proxy-handler-helpers.ts`:
- Around line 121-150: The request helper in request() lacks an explicit network
timeout, so a stalled proxy can leave tests hanging until the suite timeout. Add
a short timeout to the http.request call in request(), and make sure the timeout
handler aborts the request and rejects with a clear error so failures surface
quickly and clearly. Keep the change localized to the request() helper and
preserve the existing resolve/reject behavior for successful responses and other
request errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f9a7c8a5-3f37-44bb-a931-7c5a1a3f9688

📥 Commits

Reviewing files that changed from the base of the PR and between b3261de and f477be2.

📒 Files selected for processing (13)
  • src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact-test-helpers.ts
  • src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts
  • src/lib/onboard/dashboard-access.test.ts
  • src/lib/shields/audit-format.test.ts
  • test/e2e/support/common-egress-agent-helpers.test.ts
  • test/hermes-gateway-pid-cleanup-helpers.ts
  • test/hermes-gateway-pid-cleanup.test.ts
  • test/nemoclaw-start-extra-placeholder-breadcrumb-helpers.ts
  • test/nemoclaw-start-extra-placeholder-breadcrumb.test.ts
  • test/ollama-auth-proxy-handler-helpers.ts
  • test/ollama-auth-proxy-handler.test.ts
  • test/openclaw-device-approval-policy.test.ts
  • tsconfig.runtime-preloads.json
✅ Files skipped from review due to trivial changes (1)
  • tsconfig.runtime-preloads.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/e2e/support/common-egress-agent-helpers.test.ts
  • src/lib/shields/audit-format.test.ts
  • src/lib/onboard/dashboard-access.test.ts
  • src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts

Comment on lines +24 to +33
return function (request: unknown, ..._rest: unknown[]) {
const loaded = request === absolutePath ? patchedModule : {};
const isQrcodeRequest = typeof request === "string" && request.indexOf("qrcode") !== -1;
const patched = isQrcodePackage(loaded)
? patchQrcode(loaded)
: isQrcodeTerminalPackage(loaded)
? patchQrcodeTerminal(loaded)
: loaded;
return isQrcodeRequest ? patched : loaded;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Patch side effects leak through the "non-qrcode-request" branch, weakening the test guarantee.

patchQrcode/patchQrcodeTerminal mutate mod in place (setting __nemoclawCompactPatched and wrapping toString/generate) and return the same reference. Here, patched is computed unconditionally from loaded's shape (Lines 27-31) before checking isQrcodeRequest (Line 32). When request === absolutePath but the request string doesn't contain "qrcode", loaded (= patchedModule) still gets mutated as a side effect of computing patched, even though the function returns loaded (which is now the same mutated object). The isQrcodeRequest guard therefore doesn't actually prevent patching — it only decides which variable name is returned, not whether the mutation happened.

This defeats the doc comment's claim ("applies the compact patch to any request whose string contains 'qrcode', and passes everything else through") and could let tests pass without truly exercising the "should NOT patch a non-qrcode request" case, since the object is patched regardless.

🐛 Proposed fix: guard the patch calls behind `isQrcodeRequest`
   return function (request: unknown, ..._rest: unknown[]) {
     const loaded = request === absolutePath ? patchedModule : {};
     const isQrcodeRequest = typeof request === "string" && request.indexOf("qrcode") !== -1;
-    const patched = isQrcodePackage(loaded)
-      ? patchQrcode(loaded)
-      : isQrcodeTerminalPackage(loaded)
-        ? patchQrcodeTerminal(loaded)
-        : loaded;
-    return isQrcodeRequest ? patched : loaded;
+    if (!isQrcodeRequest) return loaded;
+    if (isQrcodePackage(loaded)) return patchQrcode(loaded);
+    if (isQrcodeTerminalPackage(loaded)) return patchQrcodeTerminal(loaded);
+    return loaded;
   };

As per path instructions for test files, "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

📝 Committable suggestion

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

Suggested change
return function (request: unknown, ..._rest: unknown[]) {
const loaded = request === absolutePath ? patchedModule : {};
const isQrcodeRequest = typeof request === "string" && request.indexOf("qrcode") !== -1;
const patched = isQrcodePackage(loaded)
? patchQrcode(loaded)
: isQrcodeTerminalPackage(loaded)
? patchQrcodeTerminal(loaded)
: loaded;
return isQrcodeRequest ? patched : loaded;
};
return function (request: unknown, ..._rest: unknown[]) {
const loaded = request === absolutePath ? patchedModule : {};
const isQrcodeRequest = typeof request === "string" && request.indexOf("qrcode") !== -1;
if (!isQrcodeRequest) return loaded;
if (isQrcodePackage(loaded)) return patchQrcode(loaded);
if (isQrcodeTerminalPackage(loaded)) return patchQrcodeTerminal(loaded);
return loaded;
};
🤖 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/messaging/channels/whatsapp/runtime/whatsapp-qr-compact-test-helpers.ts`
around lines 24 - 33, The non-qrcode branch is still triggering the qrcode
patching side effects because `patchQrcode` and `patchQrcodeTerminal` are
evaluated before `isQrcodeRequest` is checked. Update the module loader function
in `whatsapp-qr-compact-test-helpers` so the `isQrcodeRequest` guard wraps the
patching logic itself, and only compute `patched` when the request string
actually contains "qrcode"; otherwise return the unmodified `loaded` value.

Source: Path instructions

Comment on lines +49 to +85
export function runRefresh(config: unknown, env: Record<string, string> = {}): RunResult {
const src = fs.readFileSync(START_SCRIPT, "utf-8");
const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-extra-placeholder-"));
const openclawDir = path.join(root, ".openclaw");
fs.mkdirSync(openclawDir, { recursive: true });
const configPath = path.join(openclawDir, "openclaw.json");
const hashPath = path.join(openclawDir, ".config-hash");
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
fs.writeFileSync(hashPath, "oldhash\n");

const fn = extractShellFunction(src, "refresh_openclaw_provider_placeholders").replaceAll(
"/sandbox/.openclaw",
openclawDir,
);
// Stub the config-mutability guards and the dir-owner probe so the helper
// runs on a mutable temp dir without touching real sandbox ownership. This
// isolates the extras-validation + placeholder-rewrite path under test.
const wrapper = [
"#!/usr/bin/env bash",
"set -eu",
"openclaw_config_dir_owner() { echo sandbox; }",
"prepare_openclaw_config_for_write() { :; }",
"restore_openclaw_config_after_write() { :; }",
fn,
"refresh_openclaw_provider_placeholders",
].join("\n");
const script = path.join(root, "run.sh");
fs.writeFileSync(script, wrapper, { mode: 0o700 });
const result = spawnSync("bash", [script], {
encoding: "utf-8",
env: { PATH: process.env.PATH || "", ...env },
timeout: 5000,
});
const updated = JSON.parse(fs.readFileSync(configPath, "utf-8"));
fs.rmSync(root, { recursive: true, force: true });
return { result, config: updated };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Temp dir leaks if spawnSync fails or output is malformed.

fs.rmSync(root, ...) at Line 83 only runs after JSON.parse(fs.readFileSync(configPath, "utf-8")) succeeds (Line 82). If the wrapped script errors out or writes invalid JSON, JSON.parse throws and root is never cleaned up, leaking temp directories on every such failure (accumulating in CI over repeated runs).

🧹 Proposed fix
   const script = path.join(root, "run.sh");
   fs.writeFileSync(script, wrapper, { mode: 0o700 });
-  const result = spawnSync("bash", [script], {
-    encoding: "utf-8",
-    env: { PATH: process.env.PATH || "", ...env },
-    timeout: 5000,
-  });
-  const updated = JSON.parse(fs.readFileSync(configPath, "utf-8"));
-  fs.rmSync(root, { recursive: true, force: true });
-  return { result, config: updated };
+  try {
+    const result = spawnSync("bash", [script], {
+      encoding: "utf-8",
+      env: { PATH: process.env.PATH || "", ...env },
+      timeout: 5000,
+    });
+    const updated = JSON.parse(fs.readFileSync(configPath, "utf-8"));
+    return { result, config: updated };
+  } finally {
+    fs.rmSync(root, { recursive: true, force: true });
+  }
}
📝 Committable suggestion

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

Suggested change
export function runRefresh(config: unknown, env: Record<string, string> = {}): RunResult {
const src = fs.readFileSync(START_SCRIPT, "utf-8");
const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-extra-placeholder-"));
const openclawDir = path.join(root, ".openclaw");
fs.mkdirSync(openclawDir, { recursive: true });
const configPath = path.join(openclawDir, "openclaw.json");
const hashPath = path.join(openclawDir, ".config-hash");
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
fs.writeFileSync(hashPath, "oldhash\n");
const fn = extractShellFunction(src, "refresh_openclaw_provider_placeholders").replaceAll(
"/sandbox/.openclaw",
openclawDir,
);
// Stub the config-mutability guards and the dir-owner probe so the helper
// runs on a mutable temp dir without touching real sandbox ownership. This
// isolates the extras-validation + placeholder-rewrite path under test.
const wrapper = [
"#!/usr/bin/env bash",
"set -eu",
"openclaw_config_dir_owner() { echo sandbox; }",
"prepare_openclaw_config_for_write() { :; }",
"restore_openclaw_config_after_write() { :; }",
fn,
"refresh_openclaw_provider_placeholders",
].join("\n");
const script = path.join(root, "run.sh");
fs.writeFileSync(script, wrapper, { mode: 0o700 });
const result = spawnSync("bash", [script], {
encoding: "utf-8",
env: { PATH: process.env.PATH || "", ...env },
timeout: 5000,
});
const updated = JSON.parse(fs.readFileSync(configPath, "utf-8"));
fs.rmSync(root, { recursive: true, force: true });
return { result, config: updated };
}
export function runRefresh(config: unknown, env: Record<string, string> = {}): RunResult {
const src = fs.readFileSync(START_SCRIPT, "utf-8");
const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-extra-placeholder-"));
const openclawDir = path.join(root, ".openclaw");
fs.mkdirSync(openclawDir, { recursive: true });
const configPath = path.join(openclawDir, "openclaw.json");
const hashPath = path.join(openclawDir, ".config-hash");
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
fs.writeFileSync(hashPath, "oldhash\n");
const fn = extractShellFunction(src, "refresh_openclaw_provider_placeholders").replaceAll(
"/sandbox/.openclaw",
openclawDir,
);
// Stub the config-mutability guards and the dir-owner probe so the helper
// runs on a mutable temp dir without touching real sandbox ownership. This
// isolates the extras-validation + placeholder-rewrite path under test.
const wrapper = [
"#!/usr/bin/env bash",
"set -eu",
"openclaw_config_dir_owner() { echo sandbox; }",
"prepare_openclaw_config_for_write() { :; }",
"restore_openclaw_config_after_write() { :; }",
fn,
"refresh_openclaw_provider_placeholders",
].join("\n");
const script = path.join(root, "run.sh");
fs.writeFileSync(script, wrapper, { mode: 0o700 });
try {
const result = spawnSync("bash", [script], {
encoding: "utf-8",
env: { PATH: process.env.PATH || "", ...env },
timeout: 5000,
});
const updated = JSON.parse(fs.readFileSync(configPath, "utf-8"));
return { result, config: updated };
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
}
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 49-49: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(START_SCRIPT, "utf-8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 55-55: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(configPath, ${JSON.stringify(config, null, 2)}\n)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 56-56: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(hashPath, "oldhash\n")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 75-75: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(script, wrapper, { mode: 0o700 })
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 81-81: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(configPath, "utf-8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 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 `@test/nemoclaw-start-extra-placeholder-breadcrumb-helpers.ts` around lines 49
- 85, The temp directory cleanup in runRefresh should always run even when
spawnSync fails or the config file cannot be parsed. Move the fs.rmSync(root,
...) cleanup into a finally-style path around the JSON.parse/readback logic, so
root is removed regardless of failures. Use runRefresh, spawnSync, and the
config read/parse block as the anchor points when updating the helper.

Comment on lines +80 to +96
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("proxy did not start in time")), 5_000);
const tryConnect = (): void => {
const req = http.request(
{ host: "127.0.0.1", port: proxyPort, path: "/", method: "GET" },
(res) => {
res.resume();
clearTimeout(timer);
resolve();
},
);
req.on("error", () => setTimeout(tryConnect, 100));
req.end();
};
child.once("exit", (code) => reject(new Error(`proxy exited early with code ${code}`)));
tryConnect();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Retry loop in startProxy keeps running after the promise settles.

When the outer 5s timer fires and rejects, nothing stops req.on("error", () => setTimeout(tryConnect, 100)) from continuing to schedule reconnect attempts. This is a harmless-but-wasteful leak in the happy path, but if the child later starts responding, a stray tryConnect could still fire and call resolve()/clearTimeout() on an already-settled promise (no-op, but confusing to debug) and keep polling a proxy from a test that already failed/torn down.

🔧 Proposed fix: add a cancellation flag
   await new Promise<void>((resolve, reject) => {
+    let settled = false;
     const timer = setTimeout(() => reject(new Error("proxy did not start in time")), 5_000);
     const tryConnect = (): void => {
+      if (settled) return;
       const req = http.request(
         { host: "127.0.0.1", port: proxyPort, path: "/", method: "GET" },
         (res) => {
           res.resume();
+          settled = true;
           clearTimeout(timer);
           resolve();
         },
       );
-      req.on("error", () => setTimeout(tryConnect, 100));
+      req.on("error", () => {
+        if (!settled) setTimeout(tryConnect, 100);
+      });
       req.end();
     };
-    child.once("exit", (code) => reject(new Error(`proxy exited early with code ${code}`)));
+    child.once("exit", (code) => {
+      settled = true;
+      reject(new Error(`proxy exited early with code ${code}`));
+    });
     tryConnect();
   });
🤖 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 `@test/ollama-auth-proxy-handler-helpers.ts` around lines 80 - 96, The retry
loop in startProxy keeps rescheduling tryConnect after the promise has already
settled, so add a cancellation/settled flag in the helper around the
http.request retry logic. Update the startProxy Promise flow to set that flag on
resolve, reject, and the child.once("exit") path, and guard both the
req.on("error") retry scheduling and the success callback so no further
reconnect attempts or late resolve/clearTimeout calls happen once settled.

@wscurran wscurran added area: ci CI workflows, checks, release automation, or GitHub Actions area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery chore Build, CI, dependency, or tooling maintenance labels Jul 1, 2026
@wscurran

wscurran commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

…concile-shell-units

# Conflicts:
#	test/e2e/live/openclaw-inference-switch.test.ts
#	test/hermes-env-secret-boundary-hardening.test.ts
#	test/hermes-start.test.ts
#	test/openclaw-device-approval-policy.test.ts
- whatsapp-qr-compact: extract pure resolvePatchedModule so the runtime hook
  and its test share one routing decision instead of a re-implemented copy,
  and so a non-qrcode request never mutates the loaded module as a side effect
- dashboard-access: use vi.stubEnv/vi.unstubAllEnvs instead of a manual
  process.env snapshot/restore
- nemoclaw-start placeholder breadcrumb helper: rm the temp dir in finally so
  a spawn/JSON.parse failure cannot leak it
- ollama auth proxy handler helper: add a settled flag so the startup retry
  loop stops once the promise resolves/rejects

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
@prekshivyas

Copy link
Copy Markdown
Collaborator Author

Rebased onto latest main (merge) and addressed the CodeRabbit review.

Merge conflicts (4 test files):

  • openclaw-inference-switch.test.ts — kept both sides' imports/tests (both symbol sets are used in the merged body).
  • hermes-env-secret-boundary-hardening.test.ts / hermes-start.test.ts — the boundary-timeout no-op override now uses the command builtin. env -- is unsupported by macOS/BSD env(1), and an empty () array trips set -u on bash 3.2; command is a portable single-element no-op. Kept the _HERMES_PYTHON line from main (the validator now calls $_HERMES_PYTHON).
  • openclaw-device-approval-policy.test.ts — main's chore(openclaw): upgrade to 2026.6.10 and harden runtime integration #5595 (OpenClaw 2026.6.10) removed recover_failed_scope_approval, so the recovery test suite was dropped (it tested code that no longer exists). Kept and reconciled the approval_request_decision / gateway_approval_env suites against the rewritten allowlist, and folded in main's "keeps allowlisting…pure" test. Dropped the one case asserting the removed mode-based approval — the spoofed-mode rejection is already covered.

CodeRabbit findings:

  • whatsapp-qr-compact — extracted a pure resolvePatchedModule(request, loaded) in the runtime module; both installWhatsappQrCompactHook and the test helper now call it. This removes the re-implemented routing in the test and fixes the side-effect leak where a non-qrcode request could still mutate loaded.
  • dashboard-access.test.tsvi.stubEnv / vi.unstubAllEnvs instead of a manual process.env snapshot/restore.
  • nemoclaw-start placeholder breadcrumb helper — temp dir removed in a finally so a spawn/JSON.parse failure can't leak it.
  • ollama auth proxy handler helper — added a settled flag so the startup retry loop stops once the promise settles.

Verification: affected suites pass locally (device-approval, whatsapp-qr-compact, dashboard-access, hermes-start, ollama-proxy-recovery, ollama-auth-proxy-handler). The test-conditional guardrail shows no net-new if statements in any changed test file.

mockBaselineInference and its baseline constants lived in the live
openclaw-inference-switch target and were only asserted inside test(...)
blocks there, so that pure config wiring ran solely under the opt-in live
lane (the it-block guard does not catch test(...) blocks). Extract them into
openclaw-inference-switch-helpers.ts and assert them from the e2e-support
project, matching the agentReplyContainsToken backfill. The redundant live
test(...) assertion blocks are removed; the live target imports the helpers
for its runtime flow.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
@prekshivyas

Copy link
Copy Markdown
Collaborator Author

Closed the one live-only coverage gap found while auditing this branch.

mockBaselineInference (the mock-Anthropic baseline env wiring) and its constants lived in test/e2e/live/openclaw-inference-switch.test.ts and were only asserted inside test(...) blocks there — so that pure config logic ran solely under the opt-in live lane, and the unit-block guard doesn't catch test(...) (only it(...)).

  • Extracted mockBaselineInference / MOCK_BASELINE_* into openclaw-inference-switch-helpers.ts.
  • Added fast-lane assertions in test/e2e/support/openclaw-inference-switch-helpers.test.ts (exact env wiring + endpoint threading).
  • Removed the now-redundant pure test(...) blocks from the live target (the agentReplyContainsToken one was already mirrored); the live target imports the helper for its runtime flow.

Not touched: the guard still only flags it(...). Extending it to flag pure test(...) blocks would also flag openshell-version-pin.test.ts (a sync test that spawns a real resolver, unrelated to this PR), so that's better handled separately.

The second harness in this file (runRuntimeEnvValidation) was not part of the
main-merge conflict, so it kept the pre-merge shape: it ran
validate_hermes_runtime_env_secret_boundary — which main's #5595 changed to
invoke $_HERMES_PYTHON — without defining _HERMES_PYTHON, and still used the
non-portable env -- no-op. Under set -u this aborted with '_HERMES_PYTHON:
unbound variable', failing cli-test-shards (3). Align it with the start-env
harness: command builtin no-op + _HERMES_PYTHON from command -v python3.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
agentReplyContainsToken in messaging-endpoint-classifiers.ts was exported but
never called: both the live target and the e2e-support unit test assert the
route token via parseOpenClawAgentText(...).toContain(COMPAT_AGENT_REPLY)
directly, and switching them to the boolean predicate would lose the toContain
diagnostic. Keep only the shared COMPAT_AGENT_* constants; the underlying
parseOpenClawAgentText behavior stays covered by the support test.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
@prekshivyas

Copy link
Copy Markdown
Collaborator Author

PR Review Advisor (GPT-5.5) response

PRA-5 — resolved in a7ee8b2: agentReplyContainsToken in test/e2e/support/messaging-endpoint-classifiers.ts was exported but never called — both the live target and the support unit test assert the route token via parseOpenClawAgentText(...).toContain(COMPAT_AGENT_REPLY) directly (switching to the boolean predicate would lose the toContain diagnostic). Removed the dead function; kept the shared COMPAT_AGENT_* constants. Underlying behavior stays covered by messaging-endpoint-classifiers.test.ts.

The remaining four are resolve-or-justify (0 required). Justifications below.

PRA-1 — Live E2E unit-block guard (architecture) — justified

This is a preventive repository lint, not a runtime workaround masking an invalid state. The convention it enforces is real and intentional: vitest.config.ts only collects test/e2e/live/**/*.test.ts under the opt-in e2e-live project (gated by NEMOCLAW_RUN_LIVE_E2E), so a vitest unit primitive it(...) placed there never runs in normal CI, yielding silent false coverage.

  • Source of truth: the e2e-live project globs in vitest.config.ts.
  • Source-fix constraint: live files must stay in that gated project (they need real sandboxes), so the fix is not to change collection but to keep pure assertions out of gated files — which this PR does by extracting to *-helpers.ts + e2e-support.
  • Removal condition: retire the guard if the runner gains first-class support for co-locating fast unit assertions with live cases such that the unit parts execute in fast CI.
  • Regression test: test/no-unit-blocks-in-live-e2e.test.ts (pattern detection + formatting). Symlink robustness is tracked under PRA-3.

PRA-2 — Ciao os.networkInterfaces() crash-loop guard (architecture) — justified (out of scope)

The guard monkeypatch lives in nemoclaw-blueprint/scripts/ciao-network-guard.js, which is not modified by this PR (unchanged vs origin/main). This PR only adds start.sh guard-chain recovery coverage. The source-of-truth review for the guard's runtime behavior belongs to the change that introduced it, not this test-backfill PR.

PRA-3 — guard follows symlinks while walking live files (security) — justified (low risk); happy to harden

walkFiles() runs only over the repo-controlled test/e2e/live/** subtree, during CI/local dev, over already-trusted checked-out source — not attacker-supplied runtime input. A redirecting symlink would have to be committed and pass review, and the check is a dev/CI hygiene lint, not a runtime security boundary, so the practical risk is low. That said, the suggested hardening (lstatSync() + skip symlinks + track visited realpaths) is small and reasonable — I'm happy to apply it here (it would also supply PRA-1's symlink regression test) if maintainers prefer resolve over justify.

PRA-4 — Ciao guard tested via stubs, not the packaged preload (acceptance) — justified (scope)

test/nemoclaw-start-guard-recovery.test.ts targets the start.sh guard-chain recovery orchestration — re-staging packaged guards, failing closed when a guard is absent, and mirroring the restore warning into the gateway log (#6065) — exercised with stub guard files. That is the behavior this PR backfills. Asserting the Ciao guard's own runtime contract (os.networkInterfaces() throwing → observes {}) is a test of ciao-network-guard.js's logic, which this PR does not change; it belongs with that guard's package rather than this recovery-path coverage. Reasonable as a follow-up.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

E2E Target Results — ❌ Some jobs failed

Run: 28815809174
Workflow ref: test/mock-recovery-reconcile-shell-units
Requested targets: (default — all supported)
Requested jobs: (selector rejected by workflow validation)
Summary: 0 passed, 1 failed, 0 cancelled, 0 skipped

Job Result
generate-matrix ❌ failure

Failed jobs: generate-matrix. Check run artifacts for logs.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

E2E Target Results — ✅ All requested jobs passed

Run: 28816134534
Workflow ref: test/mock-recovery-reconcile-shell-units
Requested targets: (default — all supported)
Requested jobs: full-e2e,messaging-compatible-endpoint,issue-4462-scope-upgrade-approval,messaging-providers,cloud-onboard
Summary: 5 passed, 0 failed, 0 cancelled, 0 skipped

Job Result
cloud-onboard ✅ success
full-e2e ✅ success
issue-4462-scope-upgrade-approval ✅ success
messaging-compatible-endpoint ✅ success
messaging-providers ✅ success

@jyaunches
jyaunches self-requested a review July 6, 2026 19:27
@cv cv added the v0.0.75 label Jul 6, 2026
@jyaunches
jyaunches merged commit 2d1eaf1 into main Jul 6, 2026
42 checks passed
@jyaunches
jyaunches deleted the test/mock-recovery-reconcile-shell-units branch July 6, 2026 20:49
Hadar301 pushed a commit to Hadar301/NemoClaw-OpenShift that referenced this pull request Jul 12, 2026
…st it (NVIDIA#6086)

## Summary

Two regressions from NVIDIA#5874 (fixed in NVIDIA#6065) only surfaced in **live E2E
targets that don't run on PR CI**. This PR closes that class of gap: it
audits the live suite for behavior-critical assertions that are cheaply
mockable, backfills them as fast units that run on **every** PR, and
adds a guard so pure-unit blocks can't hide in live files again.

## What's here

**1. The two direct NVIDIA#6065 regression fences** (mocked shell-units)
- `reconcile`: an explicit `NEMOCLAW_MODEL_OVERRIDE` survives a
divergent gateway model and the stale in-file fallback; normal
drift-correction still runs when unset.
- `guard recovery`: the restore warning is mirrored into
`_NEMOCLAW_GATEWAY_LOG` (the marker the crash-loop E2E polls), and stays
silent when the chain is healthy.

**2. High-priority mockable backfill** (security/recovery class)
- ollama-auth-proxy: Bearer enforcement, no `/api/tags` bypass (NVIDIA#3338),
header stripping, non-ASCII auth no-crash (NVIDIA#4820), backend 502.
- `config get`: credential redaction + `gateway`-key omission (the
`nvapi-` regression class).
- device approval policy: scope-upgrade allowlist gate, gateway-env
stripping, recover-failed rejection paths (NVIDIA#4462).
- shields audit JSONL: credentials redacted before persistence.
- hermes env secret boundary: value-shape (not key-name) discriminator;
raw secrets rejected without echoing.
- dashboard bind: `NEMOCLAW_DASHBOARD_BIND` opt-in incl. negative cases
(NVIDIA#3259).
- whatsapp compact QR: package shape-detection + terminal-only `small`
(NVIDIA#4522).

**3. Medium/low backfill**
- ollama token-file lifecycle (0600 / persisted / divergent-repair);
extra-placeholder-keys canonical placeholder + accepted-keys breadcrumb;
hermes `remove_stale_gateway_file` symlink-safety; token-rotation
selective-rebuild naming; `_validate_port` fail-closed; snapshot `help`
branch.

**4. Regression guard**
- `scripts/checks/no-unit-blocks-in-live-e2e.ts` bans the vitest
`it(...)` primitive inside `test/e2e/live/**` (that glob is uncollected
on PR CI, so such blocks never run). Wired into the checks registry with
its own unit test.
- Relocated the existing offenders (skill-agent +
messaging-compatible-endpoint classifier blocks, plus the bare-`test(`
unit cases in common-egress + openclaw-inference-switch) into importable
`test/e2e/support` modules with PR-collected tests; the live tests
import them unchanged.

## Notes

- Minimal behavior-preserving refactor to `whatsapp-qr-compact.ts` to
export its pure helpers (the preload still auto-installs on require);
`tsconfig.runtime-preloads.json` excludes the new co-located test from
the shipped preload build.
- `nemoclaw-start.sh`: made two possibly-empty-array iterations
bash-3.2-safe via the existing `"${arr[@]+...}"` idiom so the shell-unit
harnesses run on stock macOS bash.
- **Deferred (2 low-value items):** the install.sh "Resolved install
ref:" log assertion and the OpenClaw anthropic plain-baseUrl assertion
both land in legacy-budget-capped test files where the growth guardrail
forbids bumping the budget; the anthropic contract is already covered on
the Hermes side and enforced live on the OpenClaw side.

## Verification

- Every new/changed test verified green individually across the `cli`,
`integration`, and `e2e-support` projects.
- `npm run checks` (incl. the new live-unit-block guard), test-file-size
budget, gitleaks, and CLI typecheck all pass.
- The full `test-cli` pre-commit hook was skipped locally only because
it trips on **pre-existing** macOS bash 3.2 failures in untouched
shell-harness suites (`select`/`set -u`); CI runs bash 5.x, where they
are green.

---
Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>


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

* **Bug Fixes**
* Improved startup robustness for environment parsing and background
launch behavior.
* WhatsApp compact-QR rendering more consistently uses the compact
“terminal” style.
* Dashboard remote bind activates only when explicitly opted in via
`NEMOCLAW_DASHBOARD_BIND=0.0.0.0`.
* Tightened audit/config redaction to prevent secret leakage and omit
gateway details.
* **Tests**
* Expanded coverage for guard-chain recovery warnings, model override
precedence, Hermes env-boundary hardening, and proxy/policy correctness.
* **Chores**
* Added a CI safeguard to prevent unit-test primitives from being
included in live E2E tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ci CI workflows, checks, release automation, or GitHub Actions area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery chore Build, CI, dependency, or tooling maintenance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants