Skip to content

refactor(types): tighten repo-wide type boundaries#2422

Merged
cv merged 16 commits into
mainfrom
autoresearch/finalize/01-consolidated-type-precision
Apr 24, 2026
Merged

refactor(types): tighten repo-wide type boundaries#2422
cv merged 16 commits into
mainfrom
autoresearch/finalize/01-consolidated-type-precision

Conversation

@cv

@cv cv commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR completes the repo-wide type-safety cleanup by removing the remaining
// @ts-nocheck markers and tightening broad escape hatches across the CLI,
plugin, blueprint, scripts, and tests. It replaces any/unknown/assertion-
heavy paths with narrower local domains while keeping the full correctness gate
green.

Changes

  • remove all remaining // @ts-nocheck markers and retire explicit any,
    Record<string, unknown>, type assertions, and non-null assertions that were
    still inflating the hotspot report
  • tighten high-churn CLI/onboarding paths in src/lib/onboard.ts,
    src/lib/onboard-session.ts, src/lib/runner.ts, src/lib/credentials.ts,
    src/lib/deploy.ts, and related helpers with narrower persisted-session,
    errno, config, and subprocess types
  • tighten plugin and blueprint parsing paths in
    nemoclaw/src/blueprint/runner.ts, snapshot.ts, state.ts,
    onboard/config.ts, and commands/migration-state.ts with concrete source
    shapes around YAML and JSON boundaries
  • update scripts and tests, including scripts/type-safety-hotspots.ts,
    scripts/bump-version.ts, and many test/*.ts files, so the stricter typing
    is exercised without weakening lint, TypeScript, or test gates
  • reduce the tracked type-safety hotspots from
    59 @ts-nocheck / 22 explicit any / 83 record unknown / 229 type assertions / 50 non-null assertions / 251 unknowns to 0 / 0 / 0 / 0 / 0 / 32

Type of Change

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

Verification

  • npx prek run --all-files passes
  • npm test passes
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

AI Disclosure

  • AI-assisted — tool: pi coding agent

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

Summary by CodeRabbit

  • Improvements

    • Stronger runtime validation and safer parsing across configs, snapshots, and state—reduces crashes on corrupt or malformed files and yields clearer error messages.
    • Tighter input handling for plugins, onboarding, and runtime commands to improve reliability and predictability.
  • Bug Fixes

    • More robust network/proxy and TLS handling during websocket/https upgrades to avoid misrouted requests and fallback safely.
  • Tests

    • Expanded and hardened test coverage to prevent regressions and improve stability.

Consolidated salvage branch from the autoresearch worktree.

Completed phases:
- @ts-nocheck: 59 -> 0
- explicit_any_count: 22 -> 0
- record_unknown_count: 83 -> 0
- type_assertion_count: 229 -> 0
- non_null_assertion_count: 50 -> 0
- unknown_type_count: 251 -> 32

Standard finalize-by-commit was not possible because all kept autoresearch
runs in autoresearch.jsonl resolve to the same merge-base commit
02dff1e.

This branch snapshots the validated worktree state for later splitting into a
cleaner commit/PR series.
@cv cv self-assigned this Apr 24, 2026
@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

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
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

This PR comprehensively strengthens TypeScript type safety by removing @ts-nocheck suppressions across test and source files, introducing runtime type guards and validation functions, replacing unsafe type assertions with explicit checks, and tightening function signatures from permissive unknown to concrete types.

Changes

Cohort / File(s) Summary
Blueprint & Configuration Validation
nemoclaw/src/blueprint/runner.ts, nemoclaw/src/blueprint/snapshot.ts, nemoclaw/src/blueprint/state.ts, nemoclaw/src/commands/migration-state.ts, nemoclaw/src/onboard/config.ts
Add runtime type guards (isBlueprint(), isStatePatch(), SnapshotManifestJson) to validate parsed YAML/JSON before use; replace type casts with explicit validation; hardened rollback/config state parsing.
Core Library Type Contracts
src/lib/agent-defs.ts, src/lib/agent-onboard.ts, src/lib/agent-runtime.ts, src/lib/config-io.ts, src/lib/credentials.ts, src/lib/deploy.ts, src/lib/credential-filter.ts, src/lib/sandbox-session-state.ts, src/lib/url-utils.ts, src/lib/local-inference.ts
Narrow function signatures from unknown to specific types; introduce LooseObject/PluginValue type aliases; add errno exception type guards; improve error handling with runtime validation of caught values.
JSON/YAML Parsing Helpers & Utilities
src/lib/provider-models.ts, src/lib/version.ts, src/lib/sandbox-state.ts, src/lib/shields.ts, src/lib/skill-install.ts, src/lib/tiers.ts, src/lib/usage-notice.ts, src/lib/http-probe.ts, src/lib/sandbox-config.ts, src/lib/preflight.ts
Introduce generic parseJson<T> helpers and domain-specific type guards; validate parsed JSON/YAML structures before casting; remove unsafe as assertions in parsing paths.
Registry & Session State
src/lib/registry.ts, src/lib/onboard-session.ts, src/lib/stale-dist-check.ts, src/lib/runner.ts
Add errno-aware type guards for lock/error handling; strengthen session JSON validation with SessionJsonValue type; improve error message extraction without unsafe casts; refactor redaction logic with typed property readers.
Runtime Recovery & Platform
src/lib/sandbox-create-stream.ts, src/lib/policies.ts, src/lib/services.ts, src/lib/inference-config.ts, src/lib/runtime-recovery.ts, src/lib/openshell.ts
Tighten interface contracts for process spawning; add explicit TypeScript models for policy/preset metadata; improve type inference for service names; remove permissive index signatures.
Scripts & Build Tools
scripts/bump-version.ts, scripts/check-coverage-ratchet.ts, scripts/dev-tier-selector.js, scripts/migrate-js-to-ts.ts, scripts/ts-migration-assist.ts, scripts/type-safety-hotspots.ts, scripts/validate-configs.ts
Add generic parseJson/parseYaml helpers; introduce type guards for coverage/manifest data; replace @ts-nocheck with explicit typings; improve CLI argument validation via requireDefined utilities.
Proxy & WebSocket Handling
nemoclaw-blueprint/scripts/ws-proxy-fix.js, nemoclaw-blueprint/scripts/ws-proxy-fix.ts
Use Reflect.get/Reflect.set for patch idempotency; introduce callOriginalRequest helper to normalize https.request overload signatures; tighten TLS servername type checking and host fallback logic.
Public API Type Refinements
nemoclaw/src/index.ts
Introduce PluginValue/ToolParams type aliases; refine OpenClawConfig index signature from unknown to PluginValue; tighten plugin hook callback parameter types; add helper readers for defensive config/params extraction.
Test Files (Type Checking Enabled)
test/cli.test.ts, test/config-set.test.ts, test/credential-rotation.test.ts, test/credentials.test.ts, test/debug-command.test.ts, test/e2e/brev-e2e.test.ts, test/gateway-cleanup.test.ts, test/gateway-liveness-probe.test.ts, test/gateway-state-reconcile-2276.test.ts, test/gemini-probe-auth.test.ts, test/http-proxy-fix-sync.test.ts, test/image-cleanup.test.ts, test/install-npm-resolution.test.ts, test/install-openshell-version-check.test.ts, test/install-preflight.test.ts, test/legacy-path-guard.test.ts, test/nemoclaw-cli-recovery.test.ts, test/ollama-proxy-recovery.test.ts, test/onboard-readiness.test.ts, test/onboard-selection.test.ts, test/onboard-session.test.ts, test/onboard.test.ts, test/openclaw-data-ownership.test.ts, test/platform.test.ts, test/policies.test.ts, test/policy-tiers.test.ts, test/presets-checkbox.test.ts, test/rebuild-policy-presets.test.ts, test/registry.test.ts, test/repro-2201.test.ts, test/resolve-openshell.test.ts, test/runner.test.ts, test/runtime-shell.test.ts, test/sandbox-build-context.test.ts, test/sandbox-connect-inference.test.ts, test/sandbox-init.test.ts, test/secret-redaction.test.ts, test/security-binaries-restriction.test.ts, test/security-c2-dockerfile-injection.test.ts, test/security-c4-manifest-traversal.test.ts, test/security-method-wildcards.test.ts, test/security-sandbox-tar-traversal.test.ts, test/service-env.test.ts, test/setup-jetson.test.ts, test/shellquote-sandbox.test.ts, test/shields-audit.test.ts, test/shields.test.ts, test/skills-frontmatter.test.ts, test/smoke-macos-install.test.ts, test/snapshot.test.ts, test/ssh-known-hosts.test.ts, test/stale-dist-check.test.ts, test/uninstall.test.ts, test/usage-notice.test.ts, test/validate-blueprint.test.ts, test/validate-config-schemas.test.ts, test/wsl2-probe-timeout.test.ts
Remove @ts-nocheck directives; introduce local type definitions for test fixtures/mocks; add runtime type guards for module imports; introduce helper utilities (exitWithCode, requireMatch, parseStdoutJson<T>) to enforce non-null/valid shapes; tighten callback parameter types in predicate functions.
Supporting Test Files
test/check-docs-links.test.ts, test/credential-exposure.test.ts, test/dns-proxy.test.ts, test/exec-approvals-path-regression.test.ts, test/sandbox-session-state.test.ts
Remove @ts-nocheck suppression; minimal type annotation updates for consistency.
Additional Coverage
nemoclaw/src/blueprint/runner.test.ts, nemoclaw/src/commands/migration-state.test.ts, .agents/skills/nemoclaw-user-reference/references/commands.md
Add comprehensive unit tests for blueprint YAML validation edge cases; expand snapshot manifest validation test coverage; adjust documentation heading level.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat(cli): add type-safety hotspot report #2082: Modifies the same type-safety infrastructure (scripts/type-safety-hotspots.ts) and tightens runtime/type guards in overlapping files like src/lib/onboard-session.ts, src/lib/tiers.ts, and nemoclaw/src/commands/migration-state.ts.

Suggested labels

enhancement: feature

Suggested reviewers

  • ericksoa

Poem

🐰 With @ts-nocheck at last removed from sight,
Type guards now dancing—validating what's right!
No more unsafe casts in dark code arrays,
Each unknown is tamed in a hundred new ways.
The compiler grins wide, as runtime checks sing,
A safer codebase—what joy they will bring! 🎉

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch autoresearch/finalize/01-consolidated-type-precision

@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: 12

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (2)
src/lib/http-probe.ts (1)

79-90: ⚠️ Potential issue | 🟡 Minor

Avoid lossy [object Object] error text in probe summaries.

At Line 89, String(message) can collapse structured detail(s) payloads into unhelpful text. Since ProbeErrorDetail allows objects/arrays, this can hide useful diagnostics.

💡 Proposed fix
+function formatProbeErrorDetail(detail: ProbeErrorDetail): string {
+  if (typeof detail === "string") return detail;
+  if (typeof detail === "number" || typeof detail === "boolean") return String(detail);
+  if (detail === null) return "null";
+  try {
+    return JSON.stringify(detail);
+  } catch {
+    return String(detail);
+  }
+}
+
 export function summarizeProbeError(body = "", status = 0): string {
   if (!body) return `HTTP ${status} with no response body`;
   try {
     const parsed: ProbeErrorBody = JSON.parse(body);
@@
       parsed?.message ||
       parsed?.detail ||
       parsed?.details;
-    if (message) return `HTTP ${status}: ${String(message)}`;
+    if (message !== undefined && message !== null) {
+      return `HTTP ${status}: ${formatProbeErrorDetail(message)}`;
+    }
   } catch {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/http-probe.ts` around lines 79 - 90, The summarizeProbeError function
currently uses String(message) which collapses objects/arrays into "[object
Object]"; update summarizeProbeError (and its handling of ProbeErrorBody /
ProbeErrorDetail) to detect if message is an object or array and serialize it
with JSON.stringify (with safe fallbacks for circular refs or failures) before
interpolating into the returned `HTTP ${status}: ...` string; otherwise use
String(message) for primitives—ensure any stringify errors are caught and fall
back to a compact string so the function always returns a useful diagnostic
instead of "[object Object]".
src/lib/agent-runtime.ts (1)

61-74: ⚠️ Potential issue | 🟠 Major

AGENT_BIN is still disconnected from the launch command.

Lines 61-73 resolve and validate an executable path, but Line 74 still runs ${gatewayCmd} directly. If the agent binary exists only at binary_path and is not on PATH, recovery still fails after the preflight passes. Either build the launch command from $AGENT_BIN, or validate the exact executable referenced by gateway_command instead.

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

In `@src/lib/agent-runtime.ts` around lines 61 - 74, The launch uses gatewayCmd
directly even after resolving AGENT_BIN from binaryPath, so replace the direct
use of gatewayCmd with the resolved executable (AGENT_BIN) or rebuild gatewayCmd
to point at AGENT_BIN; specifically, after the AGENT_BIN resolution and the
check ('if [ -z "$AGENT_BIN" ]; then ... fi;') modify the nohup invocation so it
executes "$AGENT_BIN" with the same gateway subcommand/args (or validate and
substitute the executable portion of agent.gateway_command with AGENT_BIN)
instead of running ${gatewayCmd} raw, ensuring the launched process uses the
validated path.
🟡 Minor comments (10)
src/lib/provider-models.ts-36-38 (1)

36-38: ⚠️ Potential issue | 🟡 Minor

Guard null payloads before reading parsed.data.

If the body is "null", parsed.data throws a TypeError before your custom “Unexpected model catalog response” error path runs.

Proposed minimal fix
 function parseModelIds(body: string, itemKeys: Array<keyof ModelCatalogItem> = ["id"]): string[] {
-  const parsed = parseJson<ModelCatalogResponse>(body);
-  if (!Array.isArray(parsed.data)) {
+  const parsed = parseJson<ModelCatalogResponse | null>(body);
+  if (!parsed || !Array.isArray(parsed.data)) {
     throw new Error("Unexpected model catalog response: expected a top-level data array");
   }
   return parsed.data
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/provider-models.ts` around lines 36 - 38, Guard against parsed being
null before accessing parsed.data in parseModelIds: after calling
parseJson<ModelCatalogResponse>(body) (in function parseModelIds), check whether
parsed is non-null (or use parsed?.data) and only then test
Array.isArray(parsed.data); if parsed is null throw or handle the same
"Unexpected model catalog response" error path so a body of "null" does not
cause a TypeError when referencing parsed.data.
scripts/type-safety-hotspots.ts-1356-1358 (1)

1356-1358: ⚠️ Potential issue | 🟡 Minor

--root/--project can incorrectly consume another flag as value.

requireDefined only rejects undefined, so inputs like --root --json treat --json as a path and silently skip the JSON mode flag.

💡 Proposed fix
+function requireCliValue(flag: string, value: string | undefined): string {
+  if (value === undefined || value.startsWith("-")) {
+    throw new Error(`Missing value for ${flag}`);
+  }
+  return value;
+}
+
 export function parseArgs(argv: string[]): CliOptions {
@@
       case "--root":
-        options.rootDir = path.resolve(
-          requireDefined(argv[index + 1], "Missing value for --root"),
-        );
+        options.rootDir = path.resolve(requireCliValue("--root", argv[index + 1]));
         index += 1;
         break;
       case "--project":
-        projectPaths.push(requireDefined(argv[index + 1], "Missing value for --project"));
+        projectPaths.push(requireCliValue("--project", argv[index + 1]));
         index += 1;
         break;

Also applies to: 1362-1362

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

In `@scripts/type-safety-hotspots.ts` around lines 1356 - 1358, The code sets
options.rootDir using path.resolve(requireDefined(argv[index + 1], ...)) but
requireDefined only checks for undefined so flags like "--json" can be consumed
as a value; update the logic around argv[index + 1] (used when handling --root
and similarly for --project) to validate that the next token exists and does not
start with "-" before accepting it as the value, and if it is missing or looks
like a flag, throw or call requireDefined with a clear error; locate the
handling that assigns options.rootDir and the analogous assignment for --project
and add this extra check using argv, index, and requireDefined to prevent flags
being interpreted as values.
nemoclaw/src/index.ts-67-75 (1)

67-75: ⚠️ Potential issue | 🟡 Minor

Avoid deriving endpoint from provider fallback.

At Line 73, endpoint: endpoint ?? provider ?? "" can populate endpoint with a provider label/value. That can hide a missing endpoint and prevent the intended default fallback at Line 352.

Suggested fix
     return {
-      endpoint: endpoint ?? provider ?? "",
+      endpoint: endpoint ?? "",
       provider: provider ?? "",
       model: model ?? "",
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nemoclaw/src/index.ts` around lines 67 - 75, The returned config must not
assign endpoint from provider; change the return to set endpoint to endpoint ??
"" (remove the provider fallback) while keeping provider: provider ?? "" and
model: model ?? ""; in other words, stop using endpoint: endpoint ?? provider ??
"" and ensure any downstream logic that relies on a missing endpoint uses the
intended default fallback rather than a provider value—locate the return in the
parsing routine that uses parsedObject/readStringProperty and update that single
expression.
test/credential-rotation.test.ts-69-73 (1)

69-73: ⚠️ Potential issue | 🟡 Minor

The "Fresh imports" comment is misleading—require() returns cached modules without explicit cache clearing.

Calling the loaders in beforeEach doesn't bypass CommonJS caching. While vi.restoreAllMocks() clears mocks between tests, the module instances themselves remain cached. Consider clarifying the comment to reflect actual behavior, and consolidate mock cleanup into a single afterEach hook for clarity:

Suggested adjustment
-import { describe, expect, it, vi, beforeEach } from "vitest";
+import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
...
 beforeEach(() => {
-  // Fresh imports to avoid cross-test contamination
+  // Re-capture function references for each test
   ({ hashCredential, detectMessagingCredentialRotation } = loadCredentialRotationInternals());
   registry = loadRegistryModule();
 });
+
+afterEach(() => {
+  vi.restoreAllMocks();
+});

Then remove individual vi.restoreAllMocks() calls from each test block. If module state isolation is actually required, also add delete require.cache[require.resolve("../dist/lib/onboard.js")]; and corresponding cache deletion in beforeEach.

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

In `@test/credential-rotation.test.ts` around lines 69 - 73, The "Fresh imports"
comment is inaccurate because loadCredentialRotationInternals() and
loadRegistryModule() still return cached CommonJS modules; update the comment to
say "Reinitialize references to module internals (modules remain cached unless
cache is cleared)". Move vi.restoreAllMocks() calls into a single afterEach hook
to centralize mock cleanup and remove per-test restores. If you need true
module-state isolation, add explicit cache invalidation before re-requiring
(delete require.cache[require.resolve("../dist/lib/onboard.js")] and any other
target modules) in beforeEach, then re-import via the existing loaders
(loadCredentialRotationInternals and loadRegistryModule) so hashCredential and
detectMessagingCredentialRotation refer to fresh module instances.
src/lib/openshell.test.ts-29-29 (1)

29-29: ⚠️ Potential issue | 🟡 Minor

Correct the stub to match Node.js SpawnSyncReturns.output structure.

The output array returned by Node's spawnSync has the shape [null, stdout, stderr] (where output[0] is null for stdin, output[1] is stdout, output[2] is stderr). The current stub incorrectly omits the null element at index 0, creating a shape mismatch that could cause confusion or bugs in future tests that depend on correct array indexing.

Fix
-    output: [spec.stdout, spec.stderr],
+    output: [null, spec.stdout, spec.stderr],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/openshell.test.ts` at line 29, The test stub for
SpawnSyncReturns.output is missing the leading null for stdin; update the stub
used in src/lib/openshell.test.ts so the output array matches Node's spawnSync
shape (SpawnSyncReturns.output) by including a null as the first element so it
becomes [null, spec.stdout, spec.stderr], ensuring any code/tests that index
output[1]/output[2] behave correctly.
src/lib/config-io.ts-128-141 (1)

128-141: ⚠️ Potential issue | 🟡 Minor

Duplicate fallback branches silently mask JSON parse errors.

Lines 137 and 139 both return fallback. The first handles ENOENT (file not found), but line 139 catches all other errors—including SyntaxError from malformed JSON—without logging. This could mask corrupted config files.

🛡️ Consider warning on parse errors
     if (isErrnoException(errorObject) && errorObject.code === "ENOENT") {
       return fallback;
     }
+    // Log warning for unexpected errors (e.g., malformed JSON)
+    console.warn(`Warning: Failed to parse config file ${filePath}, using defaults`);
     return fallback;
   }

Alternatively, if silent fallback is intentional, remove the redundant if check:

     if (isErrnoException(errorObject) && errorObject.code === "ENOENT") {
       return fallback;
     }
-    return fallback;
+    throw error; // Surface unexpected errors like malformed JSON
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/config-io.ts` around lines 128 - 141, readConfigFile currently
returns fallback for all errors (duplicate fallback branches) which silences
JSON parse errors; update readConfigFile to distinguish ENOENT (return fallback)
from JSON parse errors thrown by parseJson and either log a warning or rethrow a
ConfigParseError (or ConfigPermissionError only for permission cases).
Specifically, keep the permission check using isPermissionError and throw
ConfigPermissionError as today, keep the isErrnoException && code === "ENOENT"
branch to return fallback, but do not fall through to a silent generic fallback:
detect parse failures from parseJson (e.g., SyntaxError) and either throw a new
ConfigParseError including toError(errorObject) or call a logger.warn with
filePath and error details before returning fallback, and ensure
parseJson/readConfigFile references are updated accordingly so parse errors are
surfaced (use function name readConfigFile and helpers
isPermissionError/isErrnoException/toError in your change).
test/onboard.test.ts-3198-3198 (1)

3198-3198: ⚠️ Potential issue | 🟡 Minor

Add TypeScript reference for Node.js types to resolve NodeJS.ProcessEnv.

The NodeJS.ProcessEnv type annotation at lines 3198, 3527, 3664, and 3795 triggers ESLint's no-undef rule because the test ESLint configuration lacks TypeScript integration. Although @types/node is installed, the ESLint parser for test/**/*.ts has no parserOptions or projectService configuration to resolve the NodeJS namespace.

Add a triple-slash reference directive at the top of the file:

Suggested fix
+/// <reference types="node" />
 // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 // SPDX-License-Identifier: Apache-2.0

Alternatively, use typeof process.env instead of NodeJS.ProcessEnv (which is already defined in ESLint globals).

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

In `@test/onboard.test.ts` at line 3198, The TypeScript NodeJS namespace isn't
resolved for the tests: either add a triple-slash directive /// <reference
types="node" /> at the very top of this test file (before any imports) so
NodeJS.ProcessEnv is available, or replace the explicit type annotations (e.g.,
the declarations named env using NodeJS.ProcessEnv at locations like the const
env variables) with the already-resolved typeof process.env; update occurrences
in functions/blocks referencing NodeJS.ProcessEnv (the const env declarations
around lines where env is defined) to one of these fixes so ESLint no-undef is
eliminated.
test/cli.test.ts-44-44 (1)

44-44: ⚠️ Potential issue | 🟡 Minor

ESLint reports 'NodeJS' is not defined — add explicit type import or use inline annotation.

The NodeJS namespace is a TypeScript global from @types/node, but ESLint's no-undef rule doesn't recognize it without explicit configuration. Consider either:

  1. Adding /// <reference types="node" /> at the top
  2. Using inline type: env: Record<string, string | undefined> = {}
🔧 Proposed fix using inline type
 function runWithEnv(
   args: string,
-  env: NodeJS.ProcessEnv = {},
+  env: Record<string, string | undefined> = {},
   timeout: number = Number(process.env.NEMOCLAW_EXEC_TIMEOUT || 10000),
 ): CliRunResult {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/cli.test.ts` at line 44, ESLint flags the use of the NodeJS namespace
for the variable env in test/cli.test.ts; fix by either adding a triple-slash
reference to `@types/node` at the top of the file (/// <reference types="node" />)
so NodeJS.ProcessEnv is recognized, or change the type of env (the declaration
`env: NodeJS.ProcessEnv = {}`) to an inline type such as Record<string, string |
undefined> to avoid the NodeJS global; update the env declaration accordingly
and run the linter to verify the error is resolved.
nemoclaw/src/blueprint/state.ts-31-33 (1)

31-33: ⚠️ Potential issue | 🟡 Minor

Reject malformed state fields instead of merging them through.

isStatePatch() only checks that the JSON root is an object. A persisted file with wrong field types still gets spread into NemoClawState, so this boundary can return "true" for shieldsDown, {} for updatedAt, etc. Build the patch from validated keys or add per-field guards before merging.

Also applies to: 78-80

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

In `@nemoclaw/src/blueprint/state.ts` around lines 31 - 33, isStatePatch currently
only checks for a non-null object and lets malformed fields pass through; update
it (and the similar logic around lines referenced) to validate individual
keys/types and construct a safe Partial<NemoClawState> from only the known,
correctly-typed fields instead of returning true for any object. Specifically,
in isStatePatch (and the corresponding merge code at the other location)
whitelist expected keys from NemoClawState (e.g., shieldsDown: boolean,
updatedAt: string|Date, etc.), perform per-field guards/coercions/validation,
and return/build a patch object containing only validated properties so
merging/spreading cannot introduce wrong-typed fields into the state.
nemoclaw/src/blueprint/runner.ts-459-460 (1)

459-460: ⚠️ Potential issue | 🟡 Minor

Preserve the raw CLI token for the unknown-action error.

With the new isAction() check, nemoclaw foo now falls through as undefined, so the final error reports Unknown action 'undefined' instead of the actual bad token. Keeping argv[0] separately makes the message actionable again.

Proposed fix
 export async function main(argv: string[] = process.argv.slice(2)): Promise<void> {
-  const action = isAction(argv[0]) ? argv[0] : undefined;
+  const rawAction = argv[0];
+  const action = isAction(rawAction) ? rawAction : undefined;
@@
     case undefined:
     default:
-      throw new Error(`Unknown action '${String(action)}'. Use: plan, apply, status, rollback`);
+      throw new Error(
+        `Unknown action '${String(rawAction)}'. Use: plan, apply, status, rollback`,
+      );
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nemoclaw/src/blueprint/runner.ts` around lines 459 - 460, The CLI now loses
the original token when using isAction(argv[0]) so unknown-action errors show
'undefined'; capture the raw first (e.g. const raw = argv[0]) and then compute
action with isAction(raw) ? raw : undefined, and update any error reporting that
currently uses action to reference the preserved raw token (raw) in the "Unknown
action" message so the actual bad token is displayed; look for usages in
function main and any downstream error/throw that references action.
🧹 Nitpick comments (19)
src/lib/services.ts (1)

106-107: Avoid dual source-of-truth for service literals.

ServiceName and SERVICE_NAMES currently duplicate the same literal, which can drift when new services are added. Consider deriving ServiceName from SERVICE_NAMES to keep one authoritative list.

Suggested refactor
-type ServiceName = "cloudflared";
-const SERVICE_NAMES: readonly ServiceName[] = ["cloudflared"];
+const SERVICE_NAMES = ["cloudflared"] as const;
+type ServiceName = (typeof SERVICE_NAMES)[number];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/services.ts` around lines 106 - 107, Currently the service literals
are duplicated in ServiceName and SERVICE_NAMES which can drift; make
SERVICE_NAMES the single source of truth (declare it as a readonly "as const"
tuple) and derive the ServiceName type from it using the indexed lookup (e.g.,
type ServiceName = typeof SERVICE_NAMES[number]); update any usages of
ServiceName or SERVICE_NAMES accordingly so only the array literal needs editing
when adding/removing services.
test/e2e/brev-e2e.test.ts (2)

124-135: ssh function uses INSTANCE_NAME directly without validation.

The function uses INSTANCE_NAME (line 131) which can be undefined. While the test suite is gated by hasRequiredVars, this function could be called before that check or in isolation. For consistency with the PR's type-safety goals, consider using requireInstanceName().

🛡️ Defensive fix
 function ssh(
   cmd: string,
   { timeout = 120_000, stream = false }: { timeout?: number; stream?: boolean } = {},
 ): string {
+  const instanceName = requireInstanceName();
   const escaped = cmd.replace(/'/g, "'\\''");
   const stdio = stream ? STREAM_STDIO : CAPTURE_STDIO;
   const result = execSync(
-    `ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR "${INSTANCE_NAME}" '${escaped}'`,
+    `ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR "${instanceName}" '${escaped}'`,
     { encoding: "utf-8", timeout, stdio },
   );
   return stream ? "" : result.trim();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/brev-e2e.test.ts` around lines 124 - 135, The ssh helper uses the
global INSTANCE_NAME directly which can be undefined; change ssh to call
requireInstanceName() to retrieve a validated instance (replace references to
INSTANCE_NAME inside ssh) so the command string always gets a non-null value;
ensure you import/require the same requireInstanceName function used elsewhere
and use its return in the SSH command construction inside function ssh.

86-92: JSON.parse result lacks runtime validation.

JSON.parse returns unknown, but the code directly returns it as Array<{ name: string; status?: string }> without validating the shape. If brev ls --json returns an unexpected format, the callers may fail with confusing errors.

Consider adding a basic runtime check or using a type guard.

🛡️ Optional: Add minimal shape validation
 function listBrevInstances(): Array<{ name: string; status?: string }> {
   try {
-    return JSON.parse(brev("ls", "--json"));
+    const parsed: unknown = JSON.parse(brev("ls", "--json"));
+    if (!Array.isArray(parsed)) return [];
+    return parsed.filter(
+      (item): item is { name: string; status?: string } =>
+        typeof item === "object" && item !== null && typeof (item as Record<string, unknown>).name === "string"
+    );
   } catch {
     return [];
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/brev-e2e.test.ts` around lines 86 - 92, The function
listBrevInstances currently returns JSON.parse(brev("ls", "--json")) cast as
Array<{name:string; status?:string}> without runtime validation; update
listBrevInstances to parse the JSON and then perform a minimal shape check (use
Array.isArray(result) and for each item verify typeof item.name === "string" and
(item.status === undefined || typeof item.status === "string")) or implement a
small type guard helper used by listBrevInstances; if validation fails, return
an empty array (or handle error consistently) so callers never receive an
unexpected shape from brev("ls","--json").
scripts/validate-configs.ts (1)

79-83: Schema parsing assumes object root without validation.

JSON.parse could return an array or primitive if the schema file is malformed. Consider adding a guard or letting TypeScript infer the type.

♻️ Add runtime validation or remove type annotation
 function loadSchema(repoRelative: string): object {
   const abs = join(REPO_ROOT, repoRelative);
-  const schema: object = JSON.parse(readFileSync(abs, "utf-8"));
-  return schema;
+  const schema: unknown = JSON.parse(readFileSync(abs, "utf-8"));
+  if (typeof schema !== "object" || schema === null || Array.isArray(schema)) {
+    throw new Error(`Schema ${repoRelative} must be a JSON object`);
+  }
+  return schema;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/validate-configs.ts` around lines 79 - 83, The loadSchema function
currently calls JSON.parse and asserts the result as object, which may be an
array or primitive for malformed files; modify loadSchema to parse into an
unknown (or any) first, then perform a runtime type guard to verify the parsed
value is a non-null object and not an array (e.g., typeof result === "object" &&
result !== null && !Array.isArray(result")); if the check fails, throw a
descriptive Error mentioning the repoRelative path so callers know the file is
invalid; keep the function name loadSchema and the same return behavior when
validation passes.
test/setup-jetson.test.ts (1)

126-126: Redundant coercion before calling getExecErrorOutput.

The function signature getExecErrorOutput(error: Error | string | null | undefined) already handles all these types. The inline coercion error instanceof Error ? error : String(error) is unnecessary since String(error) for an Error object would lose the stderr property.

♻️ Simplify by passing the error directly
       } catch (error) {
-        output = getExecErrorOutput(error instanceof Error ? error : String(error));
+        output = getExecErrorOutput(error as Error | string | null | undefined);
       }

Alternatively, if you want to avoid the cast, broaden the catch variable type annotation or use a type guard inline.

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

In `@test/setup-jetson.test.ts` at line 126, The call to getExecErrorOutput is
coercing the caught value with error instanceof Error ? error : String(error),
which can strip Error-specific fields like stderr; instead, pass the caught
value directly to getExecErrorOutput (i.e., call getExecErrorOutput(error)) and,
if TypeScript complains, widen the catch variable type or add a local type guard
so the catch binding is typed as Error | string | null | undefined; update the
assignment to output accordingly and remove the redundant coercion.
nemoclaw/src/onboard/config.ts (1)

63-65: Consider making isOptionalString a type guard.

While functionally correct in this context, converting to a type guard would make the function reusable and more idiomatic.

♻️ Convert to type guard
-function isOptionalString(value: string | null | undefined): boolean {
-  return value === undefined || typeof value === "string";
+function isOptionalString(value: unknown): value is string | undefined {
+  return value === undefined || typeof value === "string";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nemoclaw/src/onboard/config.ts` around lines 63 - 65, Change isOptionalString
into a proper type guard so it can be reused: update the signature to accept
unknown (e.g., isOptionalString(value: unknown): value is string | null |
undefined) and adjust the body to explicitly allow undefined, null, or typeof
value === "string" (add the null check). Keep the function name isOptionalString
and use this new type predicate wherever the old helper is used.
src/lib/registry.ts (1)

76-78: Redundant null/object checks before isErrnoException.

The isErrnoException function already verifies error !== null && typeof error === "object", so the outer checks are duplicative.

♻️ Simplify the condition
     } catch (error) {
-      if (!(typeof error === "object" && error !== null && isErrnoException(error)) || error.code !== "EEXIST") {
+      if (!isErrnoException(error) || error.code !== "EEXIST") {
         throw error;
       }

Apply the same simplification to lines 89-92, 126-128, and 133-135.

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

In `@src/lib/registry.ts` around lines 76 - 78, Remove the redundant typeof/null
guards and rely on isErrnoException to perform those checks: change the
condition that currently reads if (!(typeof error === "object" && error !== null
&& isErrnoException(error)) || error.code !== "EEXIST") to use only
isErrnoException(error) (and the error.code check) so the branch becomes based
on !isErrnoException(error) || error.code !== "EEXIST". Apply the same
simplification to the other occurrences mentioned (the conditions at the blocks
around lines 89-92, 126-128, and 133-135) so all checks uniformly call
isErrnoException(error) without duplicating null/typeof checks; keep the
error.code comparisons (e.g., "EEXIST") intact and reference the same error
variable and isErrnoException helper.
test/snapshot.test.ts (1)

24-26: Type definitions work but could be consolidated.

The recursive types BackupValue (line 25) and BackupManifestOverrides (line 49) are mutually dependent. TypeScript hoists type declarations, so this works, but defining them adjacently would improve readability.

♻️ Suggested consolidation
-type BackupScalar = string | number | boolean | null | undefined;
-type BackupValue = BackupScalar | BackupManifestOverrides | BackupValue[];
-
-function isSandboxStateModule(
+type BackupScalar = string | number | boolean | null | undefined;
+type BackupManifestOverrides = { [key: string]: BackupValue };
+type BackupValue = BackupScalar | BackupManifestOverrides | BackupValue[];
+
+function isSandboxStateModule(

And remove the duplicate definition at line 49.

Also applies to: 49-49

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

In `@test/snapshot.test.ts` around lines 24 - 26, The two mutually recursive type
aliases BackupValue and BackupManifestOverrides should be defined together for
clarity: move or recreate the BackupManifestOverrides type adjacent to the
BackupValue declaration and keep the recursive references (BackupValue[] and
BackupManifestOverrides) intact so TypeScript's recursion still works; then
remove the duplicate BackupManifestOverrides definition elsewhere in the file to
avoid redundancy.
test/presets-checkbox.test.ts (1)

33-39: Edge case: parseResult throws if stdout is empty.

If stdout is empty or contains only whitespace, lines will be [], and lines[lines.length - 1] evaluates to undefined, causing JSON.parse(undefined) to throw. While this surfaces test failures appropriately in this context, adding a guard would make debugging easier.

♻️ Defensive check
 function parseResult(stdout: string): string[] {
   const lines = stdout.trim().split("\n").filter(Boolean);
+  if (lines.length === 0) {
+    return [];
+  }
   const parsed: Array<string | null> = JSON.parse(lines[lines.length - 1]);
   return Array.isArray(parsed)
     ? parsed.filter((entry): entry is string => typeof entry === "string")
     : [];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/presets-checkbox.test.ts` around lines 33 - 39, The parseResult function
can call JSON.parse on undefined when stdout is empty; update parseResult to
first trim/split into lines (as done), then if lines.length === 0 return an
empty array (or throw a clear Error) instead of accessing lines[lines.length -
1]; only call JSON.parse on a defined string, keep the existing type-filtering
logic (parsed and the entry type guard) unchanged so the function still returns
string[] when valid JSON is present.
src/lib/usage-notice.ts (1)

51-53: parseJson<T> provides type annotation without runtime validation.

The generic parseJson<T> function returns the parsed value typed as T, but doesn't validate the shape at runtime. For hasAcceptedUsageNotice (line 105), if the file contains null or an unexpected shape, the access to saved.acceptedVersion could throw—though the surrounding catch handles this gracefully.

Consider adding shape validation similar to the readStringProperty helpers, or documenting that callers must handle invalid shapes via try/catch.

♻️ Validate shape before access
 export function hasAcceptedUsageNotice(version: string): boolean {
   try {
-    const saved = parseJson<{ acceptedVersion?: string }>(
-      fs.readFileSync(getUsageNoticeStateFile(), "utf8"),
-    );
-    return saved.acceptedVersion === version;
+    const saved = JSON.parse(fs.readFileSync(getUsageNoticeStateFile(), "utf8"));
+    return typeof saved === "object" && saved !== null && saved.acceptedVersion === version;
   } catch {
     return false;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/usage-notice.ts` around lines 51 - 53, The parseJson<T> helper
currently asserts a generic type without runtime validation; update code so
parsing never falsely types unexpected shapes: either change parseJson<T> to
return unknown (e.g., parseJson(text): unknown) and have callers validate
shapes, or add a lightweight runtime shape check in hasAcceptedUsageNotice to
ensure the parsed object has a non-null object with a numeric/string
acceptedVersion before accessing saved.acceptedVersion; reference parseJson and
hasAcceptedUsageNotice when making the change and keep existing try/catch
behavior for malformed JSON.
src/lib/config-io.ts (1)

18-20: isErrnoException check is minimal but potentially fragile.

The guard only checks for "code" in error, which could match non-ErrnoException objects. Consider also verifying typeof error.code === "string" for stricter validation.

♻️ Stricter check
 function isErrnoException(error: object | null): error is NodeJS.ErrnoException {
-  return error !== null && "code" in error;
+  return error !== null && "code" in error && typeof (error as { code: unknown }).code === "string";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/config-io.ts` around lines 18 - 20, The type guard is too permissive:
update isErrnoException to ensure the found "code" property is actually a string
(and that error is non-null/typeof "object") before narrowing; locate the
isErrnoException function and change its predicate to check error !== null &&
typeof error === "object" && "code" in error && typeof (error as any).code ===
"string" so only real ErrnoExceptions pass the guard.
test/validate-blueprint.test.ts (1)

68-70: loadYaml<T> is a cast without runtime validation.

Unlike the loadYAML/loadJSON helpers in validate-config-schemas.test.ts that include isLooseObject guards, this generic loadYaml<T> function directly casts the parsed YAML to T. This is acceptable here since:

  1. The tests validate structure via assertions
  2. Schema validation in validate-config-schemas.test.ts covers structural correctness
  3. Invalid YAML would cause test failures

However, for consistency with the PR's type-safety goals, consider adding a shape guard or documenting that callers must validate.

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

In `@test/validate-blueprint.test.ts` around lines 68 - 70, The generic
loadYaml<T>(path: URL) currently casts YAML.parse(...) to T without runtime
validation; update loadYaml to perform a loose shape guard (e.g., reuse the
isLooseObject predicate used by loadYAML/loadJSON in
validate-config-schemas.test.ts) and return the parsed value only if the guard
passes (or throw a clear error), or alternatively add a comment/docstring on
loadYaml<T> that callers must perform their own validation—change the
implementation of loadYaml<T> or its documentation accordingly and keep the
function name loadYaml<T> so callers are easy to find.
src/nemoclaw.ts (1)

514-520: Redundant || undefined fallback.

Map.get() already returns undefined for missing keys, so || undefined is unnecessary.

Remove redundant fallback
   const liveNames = Array.from<string>(parseLiveSandboxNames(liveList.output));
   for (const name of liveNames) {
-    const metadata = metadataByName.get(name) || undefined;
+    const metadata = metadataByName.get(name);
     if (upsertRecoveredSandbox(name, metadata)) {
       recoveredFromGateway += 1;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nemoclaw.ts` around lines 514 - 520, Remove the redundant "|| undefined"
when retrieving metadata from the Map: change the assignment to const metadata =
metadataByName.get(name); (leave the loop and the call to
upsertRecoveredSandbox(name, metadata) and increment of recoveredFromGateway
unchanged) so that metadata is naturally undefined for missing keys without the
pointless fallback.
test/credentials.test.ts (1)

12-19: Consider using in operator instead of Reflect.get for type guards.

While Reflect.get works, using the in operator is more idiomatic for property existence checks in type guards and avoids potential prototype chain issues.

Use `in` operator for property checks
 function isCredentialsModule(value: object | null): value is CredentialsModule {
   return (
     value !== null &&
-    typeof Reflect.get(value, "loadCredentials") === "function" &&
-    typeof Reflect.get(value, "getCredential") === "function" &&
-    typeof Reflect.get(value, "saveCredential") === "function"
+    "loadCredentials" in value && typeof value.loadCredentials === "function" &&
+    "getCredential" in value && typeof value.getCredential === "function" &&
+    "saveCredential" in value && typeof value.saveCredential === "function"
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/credentials.test.ts` around lines 12 - 19, The type guard
isCredentialsModule should use the `in` operator instead of Reflect.get to check
property existence; update the implementation to first ensure value !== null
(and typeof value === "object"), then use `"loadCredentials" in value &&
"getCredential" in value && "saveCredential" in value` and finally verify each
is a function with typeof (value as any).loadCredentials === "function" etc.,
referencing the existing isCredentialsModule function and the
loadCredentials/getCredential/saveCredential property names.
src/lib/credentials.ts (1)

41-44: Redundant type checks in error guard.

The condition typeof error === "object" && error !== null && isErrnoException(error) is redundant since isErrnoException already performs the error !== null && typeof error === "object" checks internally (line 17).

Simplify the condition
   } catch (error) {
-    if (!(typeof error === "object" && error !== null && isErrnoException(error)) || error.code !== "ENOENT") {
+    if (!isErrnoException(error) || error.code !== "ENOENT") {
       throw error;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/credentials.ts` around lines 41 - 44, The catch block’s guard is
overly verbose: remove the redundant typeof/null checks and rely on
isErrnoException(error) which already performs them; update the condition in the
catch (currently using `typeof error === "object" && error !== null &&
isErrnoException(error) || error.code !== "ENOENT"`) to simply check
`!isErrnoException(error) || error.code !== "ENOENT"` so that
non-ErrnoExceptions or errors with a code not equal to "ENOENT" are re-thrown;
locate this change in the catch handling around the `isErrnoException` call in
src/lib/credentials.ts.
src/lib/credential-filter.ts (1)

15-17: parseJson<T> is an unchecked cast — acceptable here given downstream validation.

The wrapper casts JSON.parse output directly to T without runtime validation. This is acceptable in this file because sanitizeConfigFile immediately calls isConfigObject(parsed) to validate the shape. However, document the expectation that callers must validate the result.

📝 Optional: Add JSDoc clarifying the unsafe cast
+/**
+ * Parse JSON text as type T. Callers must validate the returned shape
+ * since this performs an unchecked cast.
+ */
 function parseJson<T>(text: string): T {
   return JSON.parse(text);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/credential-filter.ts` around lines 15 - 17, The parseJson<T> wrapper
performs an unchecked cast of JSON.parse output to T; update its documentation
(JSDoc) to state this is an unsafe cast and that callers must perform runtime
validation (e.g., callers such as sanitizeConfigFile should call
isConfigObject(parsed) before using the result) so future maintainers know
validation is required before trusting the typed value.
src/lib/onboard-session.ts (1)

32-32: Consider using Set<StepStatus> for stricter typing.

VALID_STEP_STATES is typed as Set<string> but could be Set<StepStatus> since it's derived from STEP_STATES. This would provide better type inference when checking membership.

♻️ Optional stricter typing
-const VALID_STEP_STATES = new Set<string>(STEP_STATES);
+const VALID_STEP_STATES: Set<StepStatus> = new Set(STEP_STATES);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/onboard-session.ts` at line 32, VALID_STEP_STATES is declared as
Set<string> but should use the stronger type Set<StepStatus>; update the
declaration to const VALID_STEP_STATES = new Set<StepStatus>(STEP_STATES as
StepStatus[]) or, better, ensure STEP_STATES is typed as readonly StepStatus[]
and then write const VALID_STEP_STATES = new Set<StepStatus>(STEP_STATES) so
membership checks against VALID_STEP_STATES infer StepStatus instead of string;
reference the symbols VALID_STEP_STATES and STEP_STATES when making this change.
scripts/ts-migration-bulk-fix-prs.ts (1)

119-147: Validate gh JSON before treating it as typed PR metadata.

listTargetPrs() and getPrMetadata() still trust JSON.parse() at the top level, so a non-array/non-object response can still break targets.map(...) or skew the base/maintainer checks before readPrFiles() sanitizes anything. Parsing to unknown and narrowing the outer shape would make this boundary actually type-safe.

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

In `@scripts/ts-migration-bulk-fix-prs.ts` around lines 119 - 147, listTargetPrs
and getPrMetadata currently call JSON.parse(...) and assume the result matches
PrListItem[] / PrListItem; change them to parse into unknown and validate the
outer shape before returning: for listTargetPrs, JSON.parse into unknown, assert
it's an array with each item being an object containing a numeric "number"
property (and optionally validate
"title","headRefName","baseRefName","url","maintainerCanModify"), otherwise
throw a clear error; for getPrMetadata, parse into unknown and assert it's an
object with a numeric "number" (and the expected fields, including "files")
before returning; implement small type-guard helpers (e.g., isPrListArray,
isPrObject) and use them in listTargetPrs and getPrMetadata to avoid passing
malformed data downstream from runCapture("gh", ...).
src/lib/sandbox-build-context.ts (1)

8-16: Consider exporting the new types for external consumers.

BuildContextStageResult and BuildContextStats are defined but not exported. If callers need to annotate variables with these return types, they cannot import them directly.

♻️ Suggested change to export types
-type BuildContextStageResult = {
+export type BuildContextStageResult = {
   buildCtx: string;
   stagedDockerfile: string;
 };

-type BuildContextStats = {
+export type BuildContextStats = {
   fileCount: number;
   totalBytes: number;
 };

Alternatively, add them to the existing export block:

 export {
   collectBuildContextStats,
   stageLegacySandboxBuildContext,
   stageOptimizedSandboxBuildContext,
+  type BuildContextStageResult,
+  type BuildContextStats,
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/sandbox-build-context.ts` around lines 8 - 16, The two new types
BuildContextStageResult and BuildContextStats are declared but not exported;
update the file so external callers can import them by exporting these types
(e.g., add export to their declarations or include them in the module's export
block), ensuring the exact type names BuildContextStageResult and
BuildContextStats are exported for use in annotations and return type imports.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 01b84c62-faf7-4d26-9d13-044b01587570

📥 Commits

Reviewing files that changed from the base of the PR and between 99b72c4 and 9ff237e.

📒 Files selected for processing (126)
  • bin/nemoclaw.js
  • nemoclaw-blueprint/scripts/ws-proxy-fix.js
  • nemoclaw-blueprint/scripts/ws-proxy-fix.ts
  • nemoclaw/src/blueprint/runner.ts
  • nemoclaw/src/blueprint/snapshot.ts
  • nemoclaw/src/blueprint/state.test.ts
  • nemoclaw/src/blueprint/state.ts
  • nemoclaw/src/commands/migration-state.test.ts
  • nemoclaw/src/commands/migration-state.ts
  • nemoclaw/src/index.ts
  • nemoclaw/src/onboard/config.test.ts
  • nemoclaw/src/onboard/config.ts
  • scripts/bump-version.ts
  • scripts/check-coverage-ratchet.ts
  • scripts/check-legacy-migrated-paths.ts
  • scripts/dev-tier-selector.js
  • scripts/migrate-js-to-ts.ts
  • scripts/ts-migration-assist.ts
  • scripts/ts-migration-bulk-fix-prs.ts
  • scripts/type-safety-hotspots.ts
  • scripts/validate-configs.ts
  • src/lib/agent-defs.ts
  • src/lib/agent-onboard.test.ts
  • src/lib/agent-onboard.ts
  • src/lib/agent-runtime.test.ts
  • src/lib/agent-runtime.ts
  • src/lib/config-io.ts
  • src/lib/credential-filter.ts
  • src/lib/credentials.ts
  • src/lib/debug-command.test.ts
  • src/lib/deploy.ts
  • src/lib/http-probe.test.ts
  • src/lib/http-probe.ts
  • src/lib/inference-config.ts
  • src/lib/local-inference.ts
  • src/lib/messaging-conflict.test.ts
  • src/lib/onboard-command.test.ts
  • src/lib/onboard-session.test.ts
  • src/lib/onboard-session.ts
  • src/lib/onboard.ts
  • src/lib/openshell.test.ts
  • src/lib/openshell.ts
  • src/lib/policies.ts
  • src/lib/preflight.test.ts
  • src/lib/preflight.ts
  • src/lib/provider-models.ts
  • src/lib/registry.ts
  • src/lib/runner.ts
  • src/lib/runtime-recovery.ts
  • src/lib/sandbox-build-context.ts
  • src/lib/sandbox-config.ts
  • src/lib/sandbox-create-stream.test.ts
  • src/lib/sandbox-create-stream.ts
  • src/lib/sandbox-session-state.test.ts
  • src/lib/sandbox-session-state.ts
  • src/lib/sandbox-state.ts
  • src/lib/services.ts
  • src/lib/shields-timer.ts
  • src/lib/shields.ts
  • src/lib/skill-install.ts
  • src/lib/stale-dist-check.ts
  • src/lib/tiers.ts
  • src/lib/uninstall-command.test.ts
  • src/lib/uninstall-command.ts
  • src/lib/url-utils.ts
  • src/lib/usage-notice.ts
  • src/lib/version.ts
  • src/nemoclaw.ts
  • test/check-docs-links.test.ts
  • test/cli.test.ts
  • test/config-set.test.ts
  • test/credential-exposure.test.ts
  • test/credential-rotation.test.ts
  • test/credentials.test.ts
  • test/dns-proxy.test.ts
  • test/e2e/brev-e2e.test.ts
  • test/exec-approvals-path-regression.test.ts
  • test/gateway-cleanup.test.ts
  • test/gateway-liveness-probe.test.ts
  • test/gemini-probe-auth.test.ts
  • test/http-proxy-fix-sync.test.ts
  • test/image-cleanup.test.ts
  • test/install-openshell-version-check.test.ts
  • test/install-preflight.test.ts
  • test/legacy-path-guard.test.ts
  • test/nemoclaw-cli-recovery.test.ts
  • test/nemoclaw-start.test.ts
  • test/ollama-proxy-recovery.test.ts
  • test/onboard-readiness.test.ts
  • test/onboard-selection.test.ts
  • test/onboard.test.ts
  • test/openclaw-data-ownership.test.ts
  • test/platform.test.ts
  • test/policies.test.ts
  • test/policy-tiers.test.ts
  • test/presets-checkbox.test.ts
  • test/rebuild-policy-presets.test.ts
  • test/registry.test.ts
  • test/repro-2201.test.ts
  • test/resolve-openshell.test.ts
  • test/runner.test.ts
  • test/runtime-shell.test.ts
  • test/sandbox-build-context.test.ts
  • test/sandbox-connect-inference.test.ts
  • test/sandbox-init.test.ts
  • test/secret-redaction.test.ts
  • test/security-binaries-restriction.test.ts
  • test/security-c2-dockerfile-injection.test.ts
  • test/security-c4-manifest-traversal.test.ts
  • test/security-method-wildcards.test.ts
  • test/security-sandbox-tar-traversal.test.ts
  • test/service-env.test.ts
  • test/setup-jetson.test.ts
  • test/shellquote-sandbox.test.ts
  • test/shields-audit.test.ts
  • test/shields.test.ts
  • test/skills-frontmatter.test.ts
  • test/smoke-macos-install.test.ts
  • test/snapshot.test.ts
  • test/ssh-known-hosts.test.ts
  • test/stale-dist-check.test.ts
  • test/uninstall.test.ts
  • test/usage-notice.test.ts
  • test/validate-blueprint.test.ts
  • test/validate-config-schemas.test.ts
  • test/wsl2-probe-timeout.test.ts
💤 Files with no reviewable changes (12)
  • bin/nemoclaw.js
  • test/exec-approvals-path-regression.test.ts
  • test/resolve-openshell.test.ts
  • test/security-binaries-restriction.test.ts
  • test/dns-proxy.test.ts
  • test/credential-exposure.test.ts
  • test/sandbox-build-context.test.ts
  • test/security-method-wildcards.test.ts
  • test/install-openshell-version-check.test.ts
  • test/nemoclaw-cli-recovery.test.ts
  • test/smoke-macos-install.test.ts
  • test/registry.test.ts

Comment thread nemoclaw/src/blueprint/runner.ts
Comment thread nemoclaw/src/commands/migration-state.ts
Comment thread src/lib/agent-defs.ts Outdated
Comment thread src/lib/sandbox-state.ts
Comment thread src/lib/shields-timer.ts Outdated
Comment thread test/security-c2-dockerfile-injection.test.ts Outdated
Comment thread test/service-env.test.ts Outdated
Comment thread test/ssh-known-hosts.test.ts Outdated
Comment thread test/uninstall.test.ts Outdated
Comment thread test/wsl2-probe-timeout.test.ts Outdated
cv added 2 commits April 24, 2026 01:52
Consolidated salvage branch from the autoresearch worktree.

Completed phases:
- @ts-nocheck: 59 -> 0
- explicit_any_count: 22 -> 0
- record_unknown_count: 83 -> 0
- type_assertion_count: 229 -> 0
- non_null_assertion_count: 50 -> 0
- unknown_type_count: 251 -> 32

Standard finalize-by-commit was not possible because all kept autoresearch
runs in autoresearch.jsonl resolve to the same merge-base commit
02dff1e.

This branch snapshots the validated worktree state for later splitting into a
cleaner commit/PR series.
# Conflicts:
#	scripts/ts-migration-bulk-fix-prs.ts
#	scripts/validate-configs.ts
#	src/lib/config-io.ts
#	src/lib/onboard.ts
#	src/lib/sandbox-build-context.ts
#	src/lib/shields-timer.ts
#	src/nemoclaw.ts
#	test/nemoclaw-start.test.ts
#	test/onboard.test.ts
#	test/policies.test.ts
#	test/snapshot.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
scripts/validate-configs.ts (1)

111-115: ⚠️ Potential issue | 🟡 Minor

Avoid "undefined" in formatted error details when message is missing.

If err.message is absent, the current interpolation can emit undefined '<property>'. Use a fallback message before formatting both property branches.

Suggested patch
 function formatError(err: {
   instancePath: string;
   keyword?: string;
   message?: string;
   params?: ValidationParams;
 }): string {
   const path = err.instancePath || "/";
+  const message = err.message ?? "unknown error";
   const detail = err.params?.additionalProperty
-    ? `${err.message} '${err.params.additionalProperty}'`
+    ? `${message} '${err.params.additionalProperty}'`
     : err.params?.unevaluatedProperty
-      ? `${err.message} '${err.params.unevaluatedProperty}'`
-      : err.message ?? "unknown error";
+      ? `${message} '${err.params.unevaluatedProperty}'`
+      : message;
   return `  ${path}: ${detail}`;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/validate-configs.ts` around lines 111 - 115, The formatted error
detail may produce "undefined '<property>'" when err.message is missing; update
the construction of detail (the variable that uses
err.params?.additionalProperty and err.params?.unevaluatedProperty) to first
select a safe fallback message (e.g., const baseMsg = err.message ?? "validation
error") and then interpolate baseMsg with the property names, using the same
conditional branches for err.params?.additionalProperty and
err.params?.unevaluatedProperty so the output never contains "undefined" even
when err.message is absent.
♻️ Duplicate comments (1)
test/service-env.test.ts (1)

421-421: ⚠️ Potential issue | 🔴 Critical

Fix unresolved type on Line 421 (BufferEncoding is undefined in this file).

Use the Node child_process options type directly for execFileSync string output instead of BufferEncoding here; this currently fails lint/type checks.

Suggested patch
-import { execSync, execFileSync } from "node:child_process";
+import { execSync, execFileSync, type ExecFileSyncOptionsWithStringEncoding } from "node:child_process";
@@
-        const runOpts: { encoding: BufferEncoding } = { encoding: "utf-8" };
+        const runOpts: ExecFileSyncOptionsWithStringEncoding = { encoding: "utf-8" };
#!/bin/bash
# Verify unresolved symbol usage and expected replacement in this file.
rg -n --type=ts '\bBufferEncoding\b|ExecFileSyncOptionsWithStringEncoding|from "node:child_process"' test/service-env.test.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/service-env.test.ts` at line 421, The test uses an unresolved
BufferEncoding type for runOpts; replace it with the proper Node child_process
options type used by execFileSync (ExecFileSyncOptionsWithStringEncoding) and
import that type from the Node child_process module, then declare runOpts:
ExecFileSyncOptionsWithStringEncoding = { encoding: "utf-8" } so execFileSync
calls get the correct typing; update the import to include
ExecFileSyncOptionsWithStringEncoding and adjust the runOpts symbol accordingly.
🧹 Nitpick comments (2)
test/onboard.test.ts (1)

3288-3288: ESLint configuration does not recognize TypeScript's NodeJS namespace.

The static analysis reports 'NodeJS' is not defined on lines 3288, 3617, 3754, and 3885. These are valid TypeScript type annotations—NodeJS.ProcessEnv is provided by @types/node. The ESLint no-undef rule flags this because it doesn't understand TypeScript's global namespace declarations.

Consider updating ESLint configuration to use @typescript-eslint/no-undef or disable no-undef for TypeScript files (TypeScript's own compiler handles undefined variable detection). Alternatively, you can use typeof process.env as the type annotation to avoid the linting error while maintaining type safety.

Alternative type annotation (optional)
-      const env: NodeJS.ProcessEnv = {
+      const env: typeof process.env = {

Also applies to: 3617-3617, 3754-3754, 3885-3885

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

In `@test/onboard.test.ts` at line 3288, The lint error comes from using the
TypeScript-only global type NodeJS.ProcessEnv for the test variable env; either
update ESLint to stop using the JS no-undef rule for TS (replace "no-undef" with
"@typescript-eslint/no-undef" or disable "no-undef" for TypeScript files) or
change the type annotation in the tests from "NodeJS.ProcessEnv" to a
TS-friendly alternative such as "typeof process.env" (locate the env
declarations using the "env" variable and the NodeJS.ProcessEnv annotation in
the test file and apply the chosen fix consistently across all instances).
src/lib/onboard-session.ts (1)

298-301: Preserve string-only runtime guarantees in createSession array fields.

At Lines 298-301, array contents are copied without validating element types. Since this is an exported API, runtime callers can still pass mixed arrays and violate the Session contract.

🛡️ Proposed hardening
-    policyPresets: Array.isArray(overrides.policyPresets) ? [...overrides.policyPresets] : null,
-    messagingChannels: Array.isArray(overrides.messagingChannels)
-      ? [...overrides.messagingChannels]
-      : null,
+    policyPresets: readStringArray(overrides.policyPresets ?? undefined),
+    messagingChannels: readStringArray(overrides.messagingChannels ?? undefined),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/onboard-session.ts` around lines 298 - 301, The current createSession
handling copies overrides.policyPresets and overrides.messagingChannels without
validating element types, which can violate the Session string-only arrays;
update the logic in createSession to sanitize these arrays by checking
Array.isArray(overrides.policyPresets) /
Array.isArray(overrides.messagingChannels) and creating new arrays that only
include string elements (e.g., filter each array with typeof item === 'string')
or set the field to null if no valid strings remain, ensuring the resulting
session object upholds the Session contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/onboard-session.ts`:
- Line 33: VALID_STEP_STATES is declared but never used while the same set is
rebuilt later; either use VALID_STEP_STATES where the manual Set of step states
is created (replace that manual construction with a reference to
VALID_STEP_STATES) or rename the declaration to _VALID_STEP_STATES (or remove
it) to satisfy the unused-variable rule; update references around the code that
manually re-encodes the state set (the block that currently recreates the step
states) to import/consume VALID_STEP_STATES or delete the duplicate and rely on
STEP_STATES/VALID_STEP_STATES instead.

In `@src/nemoclaw.ts`:
- Around line 160-178: The runOpenshell and captureOpenshell wrappers drop the
RunnerOptions.timeout field, so forward opts.timeout into the options passed to
runOpenshellCommand and captureOpenshellCommand; update both functions
(runOpenshell and captureOpenshell) to include timeout: opts.timeout alongside
cwd: ROOT, env, stdio/ignoreError, errorLine and exit so the underlying commands
(getOpenshellBinary, runOpenshellCommand, captureOpenshellCommand) receive the
timeout used by the connect polling logic.

In `@test/cli.test.ts`:
- Around line 42-46: The runWithEnv function's env parameter uses
NodeJS.ProcessEnv which triggers ESLint no-undef; change the env parameter type
to a structural map like Record<string, string | undefined> in the runWithEnv
signature so it accepts the same string-to-optional-string mapping without
relying on the NodeJS global type (update the signature that currently reads
env: NodeJS.ProcessEnv to env: Record<string, string | undefined>).
- Around line 28-35: readCliErrorOutput currently only handles string
stdout/stderr and drops Buffer values; update readCliErrorOutput to preserve or
convert Buffer outputs by using the existing readBufferOrStringProperty helper
(or call .toString() on Buffer) when building the out field, and keep status
handling (from error.status) the same; reference readCliErrorOutput,
readBufferOrStringProperty, CliErrorShape, stdout, stderr, and status to locate
the changes.

In `@test/policies.test.ts`:
- Line 52: The test declares envOverrides: NodeJS.ProcessEnv which triggers
ESLint no-undef; replace the NodeJS.ProcessEnv type with the imported ProcessEnv
from node:process and add an import for ProcessEnv at the top of the test file;
update the declaration envOverrides: ProcessEnv = {} and apply the same change
in other affected test files (e.g., onboard.test.ts, cli.test.ts,
runner.test.ts) where NodeJS.ProcessEnv is used so all tests import ProcessEnv
from 'node:process' and use that symbol instead.

In `@test/snapshot.test.ts`:
- Around line 40-42: The dynamic ESM import using path.join(REPO_ROOT, "dist",
"lib", "sandbox-state.js") fails on Windows; convert the filesystem path to a
file:// URL before importing. Replace the string path passed to import() (the
expression used to set loadedSandboxState) by wrapping the joined path with
pathToFileURL(...).href from the url module so
import(pathToFileURL(path.join(REPO_ROOT, "dist", "lib",
"sandbox-state.js")).href) is used to load sandbox-state.js cross-platform.

---

Outside diff comments:
In `@scripts/validate-configs.ts`:
- Around line 111-115: The formatted error detail may produce "undefined
'<property>'" when err.message is missing; update the construction of detail
(the variable that uses err.params?.additionalProperty and
err.params?.unevaluatedProperty) to first select a safe fallback message (e.g.,
const baseMsg = err.message ?? "validation error") and then interpolate baseMsg
with the property names, using the same conditional branches for
err.params?.additionalProperty and err.params?.unevaluatedProperty so the output
never contains "undefined" even when err.message is absent.

---

Duplicate comments:
In `@test/service-env.test.ts`:
- Line 421: The test uses an unresolved BufferEncoding type for runOpts; replace
it with the proper Node child_process options type used by execFileSync
(ExecFileSyncOptionsWithStringEncoding) and import that type from the Node
child_process module, then declare runOpts:
ExecFileSyncOptionsWithStringEncoding = { encoding: "utf-8" } so execFileSync
calls get the correct typing; update the import to include
ExecFileSyncOptionsWithStringEncoding and adjust the runOpts symbol accordingly.

---

Nitpick comments:
In `@src/lib/onboard-session.ts`:
- Around line 298-301: The current createSession handling copies
overrides.policyPresets and overrides.messagingChannels without validating
element types, which can violate the Session string-only arrays; update the
logic in createSession to sanitize these arrays by checking
Array.isArray(overrides.policyPresets) /
Array.isArray(overrides.messagingChannels) and creating new arrays that only
include string elements (e.g., filter each array with typeof item === 'string')
or set the field to null if no valid strings remain, ensuring the resulting
session object upholds the Session contract.

In `@test/onboard.test.ts`:
- Line 3288: The lint error comes from using the TypeScript-only global type
NodeJS.ProcessEnv for the test variable env; either update ESLint to stop using
the JS no-undef rule for TS (replace "no-undef" with
"@typescript-eslint/no-undef" or disable "no-undef" for TypeScript files) or
change the type annotation in the tests from "NodeJS.ProcessEnv" to a
TS-friendly alternative such as "typeof process.env" (locate the env
declarations using the "env" variable and the NodeJS.ProcessEnv annotation in
the test file and apply the chosen fix consistently across all instances).
🪄 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: f026301e-dd9d-4735-b596-465719754433

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff237e and 7fe9b5c.

📒 Files selected for processing (20)
  • .agents/skills/nemoclaw-user-reference/references/commands.md
  • nemoclaw/src/blueprint/snapshot.ts
  • scripts/validate-configs.ts
  • src/lib/config-io.ts
  • src/lib/local-inference.ts
  • src/lib/onboard-session.test.ts
  • src/lib/onboard-session.ts
  • src/lib/onboard.ts
  • src/lib/sandbox-build-context.ts
  • src/lib/sandbox-create-stream.ts
  • src/lib/sandbox-state.ts
  • src/nemoclaw.ts
  • test/cli.test.ts
  • test/e2e/brev-e2e.test.ts
  • test/install-openshell-version-check.test.ts
  • test/onboard.test.ts
  • test/policies.test.ts
  • test/secret-redaction.test.ts
  • test/service-env.test.ts
  • test/snapshot.test.ts
💤 Files with no reviewable changes (1)
  • test/install-openshell-version-check.test.ts
✅ Files skipped from review due to trivial changes (3)
  • .agents/skills/nemoclaw-user-reference/references/commands.md
  • src/lib/sandbox-state.ts
  • src/lib/local-inference.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • nemoclaw/src/blueprint/snapshot.ts
  • src/lib/sandbox-build-context.ts
  • test/secret-redaction.test.ts
  • src/lib/config-io.ts
  • test/e2e/brev-e2e.test.ts
  • src/lib/onboard-session.test.ts

Comment thread src/lib/onboard-session.ts Outdated
Comment thread src/nemoclaw.ts
Comment thread test/cli.test.ts
Comment thread test/cli.test.ts
Comment thread test/policies.test.ts Outdated
Comment thread test/snapshot.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

585-638: ⚠️ Potential issue | 🟡 Minor

Preserve the raw invalid action in the error message.

Right now unrecognized actions are reported as undefined, which hides the actual bad token (Line 585 + Line 637).

Suggested fix
 export async function main(argv: string[] = process.argv.slice(2)): Promise<void> {
-  const action = isAction(argv[0]) ? argv[0] : undefined;
+  const rawAction = argv[0];
+  const action = isAction(rawAction) ? rawAction : undefined;
@@
     case undefined:
     default:
-      throw new Error(`Unknown action '${String(action)}'. Use: plan, apply, status, rollback`);
+      throw new Error(
+        `Unknown action '${String(rawAction)}'. Use: plan, apply, status, rollback`,
+      );
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nemoclaw/src/blueprint/runner.ts` around lines 585 - 638, The error for
unknown actions currently shows 'undefined' because action is set to undefined
when argv[0] is invalid; update the default/error branch that throws new
Error(...) so it includes the raw token from argv[0] (or clearly indicates it's
missing) instead of action — reference the variables action and argv[0] in the
message used in the switch default/undefined case so the thrown error reports
the actual bad token.
♻️ Duplicate comments (1)
test/snapshot.test.ts (1)

40-42: ⚠️ Potential issue | 🟠 Major

Use pathToFileURL(...).href for cross-platform ESM dynamic import.

On Line 41, import(path.join(...)) can fail on Windows (ERR_UNSUPPORTED_ESM_URL_SCHEME) when the path contains a drive-letter absolute path. Convert to a file URL first.

Suggested fix
 import fs from "node:fs";
 import os from "node:os";
 import path from "node:path";
+import { pathToFileURL } from "node:url";
 import { describe, it, expect, afterAll, beforeEach } from "vitest";
@@
 const loadedSandboxState = await import(
-  path.join(REPO_ROOT, "dist", "lib", "sandbox-state.js"),
+  pathToFileURL(path.join(REPO_ROOT, "dist", "lib", "sandbox-state.js")).href,
 );
In Node.js ESM, does dynamic import of a Windows absolute filesystem path require converting the path with pathToFileURL(...).href?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/snapshot.test.ts` around lines 40 - 42, The dynamic ESM import using
import(path.join(REPO_ROOT, "dist", "lib", "sandbox-state.js")) can fail on
Windows; change the import to convert the filesystem path to a file:// URL first
by calling pathToFileURL on the path you build (using
pathToFileURL(path.join(REPO_ROOT, "dist", "lib", "sandbox-state.js")).href)
before passing it into import so loadedSandboxState receives a proper ESM file
URL; ensure you import pathToFileURL from the 'url' module and use its .href
when calling import.
🧹 Nitpick comments (2)
nemoclaw/src/commands/migration-state.test.ts (1)

932-958: Split malformed externalRoots and malformed warnings into separate tests.

At Line 951, bindings: [1] can fail validation before Line 954 is evaluated, so warnings: [null] may not be independently exercised. Separate tests would make branch coverage and failure diagnosis clearer.

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

In `@nemoclaw/src/commands/migration-state.test.ts` around lines 932 - 958, The
test "rejects malformed externalRoots and warnings entries" conflates two
validation failures; split it into two focused tests: one that crafts a snapshot
JSON with the malformed externalRoots (e.g., bindings: [1]) while keeping
warnings valid, and one that crafts a snapshot JSON with valid externalRoots but
malformed warnings (e.g., warnings: [null]); in both tests use addFile to write
the snapshot and call loadSnapshotManifest("/snapshots/snap1") with
expect(...).toThrow(/Invalid snapshot manifest/) to assert rejection so each
validation branch (externalRoots and warnings) is exercised independently.
src/lib/sandbox-state.ts (1)

42-44: Narrow parseJson to unknown to prevent future unsafe casts.

The generic T implies runtime shape safety that this function does not provide. Currently, the only call site (line 777) already uses parseJson<unknown>, which is safe. However, narrowing the function signature to return unknown directly prevents accidental unsafe usage in future changes.

Proposed change
-function parseJson<T>(text: string): T {
+function parseJson(text: string): unknown {
   return JSON.parse(text);
 }
@@
-    const parsed = parseJson<unknown>(readFileSync(manifestPath, "utf-8"));
+    const parsed = parseJson(readFileSync(manifestPath, "utf-8"));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/sandbox-state.ts` around lines 42 - 44, The parseJson generic should
be narrowed to return unknown to avoid implying runtime type safety: change the
parseJson<T>(...) signature in src/lib/sandbox-state.ts to parseJson(...):
unknown (remove the generic parameter), update its implementation to return
unknown, and adjust any callers if they assumed a concrete T (current call
already uses parseJson<unknown> so no change expected); ensure exported
types/signature reflect the non-generic function and update any tests or call
sites that performed implicit casts to instead explicitly narrow the unknown to
the expected type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/sandbox-state.ts`:
- Around line 132-149: The type guard isRebuildManifest fails to validate the
optional error field of RebuildManifest (error?: string), allowing non-string
values like numbers; update the isRebuildManifest function to explicitly check
that manifest.error is either undefined or a string (e.g., typeof manifest.error
=== 'string' || manifest.error === undefined) so objects with error: 123 are
rejected while preserving the optional nature of the field.

---

Outside diff comments:
In `@nemoclaw/src/blueprint/runner.ts`:
- Around line 585-638: The error for unknown actions currently shows 'undefined'
because action is set to undefined when argv[0] is invalid; update the
default/error branch that throws new Error(...) so it includes the raw token
from argv[0] (or clearly indicates it's missing) instead of action — reference
the variables action and argv[0] in the message used in the switch
default/undefined case so the thrown error reports the actual bad token.

---

Duplicate comments:
In `@test/snapshot.test.ts`:
- Around line 40-42: The dynamic ESM import using import(path.join(REPO_ROOT,
"dist", "lib", "sandbox-state.js")) can fail on Windows; change the import to
convert the filesystem path to a file:// URL first by calling pathToFileURL on
the path you build (using pathToFileURL(path.join(REPO_ROOT, "dist", "lib",
"sandbox-state.js")).href) before passing it into import so loadedSandboxState
receives a proper ESM file URL; ensure you import pathToFileURL from the 'url'
module and use its .href when calling import.

---

Nitpick comments:
In `@nemoclaw/src/commands/migration-state.test.ts`:
- Around line 932-958: The test "rejects malformed externalRoots and warnings
entries" conflates two validation failures; split it into two focused tests: one
that crafts a snapshot JSON with the malformed externalRoots (e.g., bindings:
[1]) while keeping warnings valid, and one that crafts a snapshot JSON with
valid externalRoots but malformed warnings (e.g., warnings: [null]); in both
tests use addFile to write the snapshot and call
loadSnapshotManifest("/snapshots/snap1") with expect(...).toThrow(/Invalid
snapshot manifest/) to assert rejection so each validation branch (externalRoots
and warnings) is exercised independently.

In `@src/lib/sandbox-state.ts`:
- Around line 42-44: The parseJson generic should be narrowed to return unknown
to avoid implying runtime type safety: change the parseJson<T>(...) signature in
src/lib/sandbox-state.ts to parseJson(...): unknown (remove the generic
parameter), update its implementation to return unknown, and adjust any callers
if they assumed a concrete T (current call already uses parseJson<unknown> so no
change expected); ensure exported types/signature reflect the non-generic
function and update any tests or call sites that performed implicit casts to
instead explicitly narrow the unknown to the expected type.
🪄 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: 4161fd4d-aaff-4ff7-bee9-2d1b66a1fa47

📥 Commits

Reviewing files that changed from the base of the PR and between 7fe9b5c and b508ea7.

📒 Files selected for processing (14)
  • nemoclaw/src/blueprint/runner.test.ts
  • nemoclaw/src/blueprint/runner.ts
  • nemoclaw/src/commands/migration-state.test.ts
  • nemoclaw/src/commands/migration-state.ts
  • src/lib/agent-defs.ts
  • src/lib/sandbox-state.ts
  • src/lib/shields.ts
  • test/install-preflight.test.ts
  • test/security-c2-dockerfile-injection.test.ts
  • test/service-env.test.ts
  • test/snapshot.test.ts
  • test/ssh-known-hosts.test.ts
  • test/uninstall.test.ts
  • test/wsl2-probe-timeout.test.ts
✅ Files skipped from review due to trivial changes (2)
  • test/security-c2-dockerfile-injection.test.ts
  • src/lib/agent-defs.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • test/ssh-known-hosts.test.ts
  • test/wsl2-probe-timeout.test.ts
  • src/lib/shields.ts
  • nemoclaw/src/commands/migration-state.ts
  • test/install-preflight.test.ts

Comment thread src/lib/sandbox-state.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

572-575: Return type inconsistency: function returns empty string but declares string | null.

getActiveGatewayName returns "" when no match is found, but the return type includes | null. While callers handle this correctly (using truthiness checks), the type contract is misleading.

♻️ Align implementation with declared type
 function getActiveGatewayName(output = ""): string | null {
   const match = stripAnsi(output).match(/^\s*Gateway:\s+(.+?)\s*$/m);
-  return match ? match[1].trim() : "";
+  return match ? match[1].trim() : null;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nemoclaw.ts` around lines 572 - 575, The function getActiveGatewayName
currently returns an empty string when no match is found but is typed to return
string | null; change the implementation to return null on no match (keep the
declared return type) so callers relying on null vs empty-string behavior remain
correct—locate getActiveGatewayName, keep the stripAnsi(output).match(...) logic
and return match ? match[1].trim() : null.
src/lib/onboard-session.ts (1)

163-167: Minor: ErrnoLike type has an unreachable union member.

The { code?: string | number } branch is never matched because isErrnoException requires error instanceof Error. The type could be simplified, but the current implementation is correct and safe.

♻️ Optional simplification
-type ErrnoLike = Error | { code?: string | number } | null;
-
-function isErrnoException(error: ErrnoLike): error is NodeJS.ErrnoException {
+function isErrnoException(error: unknown): error is NodeJS.ErrnoException {
   return error !== null && error instanceof Error && "code" in error;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/onboard-session.ts` around lines 163 - 167, The ErrnoLike union
contains an unreachable `{ code?: string | number }` branch because
isErrnoException checks instanceof Error; replace the alias with a single,
accurate type such as NodeJS.ErrnoException | null (or Error | null if you
intentionally want non-errno Errors), update the ErrnoLike declaration
accordingly, and ensure uses of ErrnoLike (and the isErrnoException guard)
continue to compile and narrow correctly; see ErrnoLike and isErrnoException for
the locations to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/nemoclaw.ts`:
- Line 3412: The filter callback's type for allImages is incorrect (it declares
id but the mapped objects have tag and size); update the type annotation used in
the filter for the variable allImages (and the callback parameter in the
expression that produces orphans) to reflect the actual shape (e.g., { tag:
string; size: number } or just { tag: string }) so that the filter on
registeredTags.has(img.tag) is type-correct; locate the usage around the const
orphans = allImages.filter((img: ...) => !registeredTags.has(img.tag))
expression and change the callback parameter type accordingly.

In `@test/policies.test.ts`:
- Around line 42-48: The helper functions parseJson and parseCalls are unused
and violate the unused-variable rule; either remove them or rename to _parseJson
and _parseCalls to silence the lint rule. Locate the declarations of
parseJson<T>(raw: string) and parseCalls(output: string): PolicyCall[] and
either delete both functions if they are not needed or prefix their names with
an underscore (e.g., _parseJson, _parseCalls) so they remain available but
satisfy the unused-variable naming guideline.

---

Nitpick comments:
In `@src/lib/onboard-session.ts`:
- Around line 163-167: The ErrnoLike union contains an unreachable `{ code?:
string | number }` branch because isErrnoException checks instanceof Error;
replace the alias with a single, accurate type such as NodeJS.ErrnoException |
null (or Error | null if you intentionally want non-errno Errors), update the
ErrnoLike declaration accordingly, and ensure uses of ErrnoLike (and the
isErrnoException guard) continue to compile and narrow correctly; see ErrnoLike
and isErrnoException for the locations to change.

In `@src/nemoclaw.ts`:
- Around line 572-575: The function getActiveGatewayName currently returns an
empty string when no match is found but is typed to return string | null; change
the implementation to return null on no match (keep the declared return type) so
callers relying on null vs empty-string behavior remain correct—locate
getActiveGatewayName, keep the stripAnsi(output).match(...) logic and return
match ? match[1].trim() : null.
🪄 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: 03a3e6df-5413-4fd1-88fb-b5b364f2ba2f

📥 Commits

Reviewing files that changed from the base of the PR and between b508ea7 and 45c531e.

📒 Files selected for processing (11)
  • src/lib/onboard-session.ts
  • src/nemoclaw.ts
  • test/cli.test.ts
  • test/gateway-state-reconcile-2276.test.ts
  • test/install-npm-resolution.test.ts
  • test/onboard.test.ts
  • test/openclaw-data-ownership.test.ts
  • test/policies.test.ts
  • test/runner.test.ts
  • test/runtime-shell.test.ts
  • test/snapshot.test.ts
✅ Files skipped from review due to trivial changes (3)
  • test/install-npm-resolution.test.ts
  • test/gateway-state-reconcile-2276.test.ts
  • test/onboard.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/runtime-shell.test.ts

Comment thread src/nemoclaw.ts Outdated
Comment thread test/policies.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
test/install-preflight.test.ts (1)

1346-1355: Consider extracting a shared env type alias for helper signatures.

The same inline Record<string, string | undefined> appears across multiple helpers. A single alias would reduce repetition and keep updates localized.

♻️ Suggested small refactor
+type EnvOverrides = Record<string, string | undefined>;
+
-function callResolveReleaseTag(fakeBin: string, env: Record<string, string | undefined> = {}) {
+function callResolveReleaseTag(fakeBin: string, env: EnvOverrides = {}) {

-function callInstallerFn(fnCall: string, env: Record<string, string | undefined> = {}) {
+function callInstallerFn(fnCall: string, env: EnvOverrides = {}) {

-function callEnsureSupportedRuntime(fakeBin: string, env: Record<string, string | undefined> = {}) {
+function callEnsureSupportedRuntime(fakeBin: string, env: EnvOverrides = {}) {

Also applies to: 1576-1585, 1851-1864

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

In `@test/install-preflight.test.ts` around lines 1346 - 1355, Extract a shared
type alias for the environment object (currently repeated as Record<string,
string | undefined>) and use it in helper function signatures like
callResolveReleaseTag (and the other helpers at the noted ranges) to avoid
repetition; add e.g. type Env = Record<string, string | undefined> at the top of
the test file and replace the inline type in callResolveReleaseTag's env
parameter and the other helper functions so all signatures use the new Env
alias.
test/runner.test.ts (1)

70-83: Consider extracting shared spawnSync mock lifecycle boilerplate.

Setup/restore/call-cache-reset logic repeats across multiple tests; consolidating it would reduce noise and lower maintenance risk.

♻️ Possible refactor sketch
+function withMockedSpawnSync(
+  impl: typeof childProcess.spawnSync,
+  runTest: () => void,
+): void {
+  const originalSpawnSync = childProcess.spawnSync;
+  // `@ts-expect-error` intentional partial mock for tests
+  childProcess.spawnSync = impl;
+  try {
+    runTest();
+  } finally {
+    childProcess.spawnSync = originalSpawnSync;
+    delete require.cache[require.resolve(runnerPath)];
+  }
+}

Then each test can focus on assertions instead of repeating lifecycle plumbing.

Also applies to: 92-104, 167-188, 197-218, 504-530

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

In `@test/runner.test.ts` around lines 70 - 83, Extract the repeated spawnSync
mock setup/teardown into a reusable helper that encapsulates creating the call
array, monkey-patching childProcess.spawnSync via captureSpawnCall, clearing
require.cache for runnerPath, and restoring the original spawnSync; update tests
that use captureSpawnCall, childProcess.spawnSync, runnerPath and require.cache
(e.g., the blocks around run/runInteractive) to call this helper at the start
and a corresponding restore/cleanup function (or use a single
withMockedSpawnSync(fn) wrapper) so each test only focuses on assertions and no
longer repeats lifecycle boilerplate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/install-preflight.test.ts`:
- Around line 1346-1355: Extract a shared type alias for the environment object
(currently repeated as Record<string, string | undefined>) and use it in helper
function signatures like callResolveReleaseTag (and the other helpers at the
noted ranges) to avoid repetition; add e.g. type Env = Record<string, string |
undefined> at the top of the test file and replace the inline type in
callResolveReleaseTag's env parameter and the other helper functions so all
signatures use the new Env alias.

In `@test/runner.test.ts`:
- Around line 70-83: Extract the repeated spawnSync mock setup/teardown into a
reusable helper that encapsulates creating the call array, monkey-patching
childProcess.spawnSync via captureSpawnCall, clearing require.cache for
runnerPath, and restoring the original spawnSync; update tests that use
captureSpawnCall, childProcess.spawnSync, runnerPath and require.cache (e.g.,
the blocks around run/runInteractive) to call this helper at the start and a
corresponding restore/cleanup function (or use a single withMockedSpawnSync(fn)
wrapper) so each test only focuses on assertions and no longer repeats lifecycle
boilerplate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c1ef95fa-05c1-4280-bf97-3605154cdfd2

📥 Commits

Reviewing files that changed from the base of the PR and between 45c531e and b68eefc.

📒 Files selected for processing (11)
  • test/cli.test.ts
  • test/gateway-state-reconcile-2276.test.ts
  • test/install-npm-resolution.test.ts
  • test/install-preflight.test.ts
  • test/onboard.test.ts
  • test/openclaw-data-ownership.test.ts
  • test/policies.test.ts
  • test/runner.test.ts
  • test/runtime-shell.test.ts
  • test/security-c2-dockerfile-injection.test.ts
  • test/uninstall.test.ts
✅ Files skipped from review due to trivial changes (3)
  • test/gateway-state-reconcile-2276.test.ts
  • test/install-npm-resolution.test.ts
  • test/onboard.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/uninstall.test.ts

@cv

cv commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator Author

I rechecked the PR at ac361485cef65238b5ff0e7d587c3818b1e67e32. That commit helps around the touched guards/tests, but I still think this needs another cleanup pass before merge.

Blocking / Requested Changes

1. Preserve legacy rebuild manifests

src/lib/sandbox-state.ts now validates RebuildManifest with:

value.blueprintDigest === null || typeof value.blueprintDigest === "string"

This rejects older valid rebuild manifests that simply do not have blueprintDigest. The result is that readManifest() returns null, and listBackups() silently drops existing backups.

Please update the guard to accept legacy manifests where blueprintDigest === undefined, while still rejecting invalid non-string values.

Acceptance criteria:

  • blueprintDigest: undefined / missing is accepted.
  • blueprintDigest: null is accepted.
  • blueprintDigest: string is accepted.
  • blueprintDigest: number/object/boolean is rejected.
  • Add a regression test in test/snapshot.test.ts proving listBackups() still returns a manifest created before blueprintDigest existed.

2. Preserve invalid blueprint runner action text

nemoclaw/src/blueprint/runner.ts still derives:

const action = isAction(argv[0]) ? argv[0] : undefined;

Then the error path reports:

Unknown action 'undefined'

So main(["bogus"]) loses the actual token. Please preserve the raw action separately and report the original invalid value.

Suggested shape:

const rawAction = argv[0];
const action = isAction(rawAction) ? rawAction : undefined;

Then use rawAction in the unknown-action message.

Ideally, validate the action before loadBlueprint() so CLI usage errors are not masked by a missing/invalid blueprint.yaml.

Acceptance criteria:

  • main(["bogus"]) rejects with an error containing bogus, not undefined.
  • main([]) still reports a clear usage/unknown-action message.
  • Invalid action handling does not require a valid blueprint file to exist.
  • Update nemoclaw/src/blueprint/runner.test.ts.

3. Align agent recovery validation with execution

src/lib/agent-runtime.ts resolves and validates AGENT_BIN, but then launches gatewayCmd:

AGENT_BIN=...
if [ -z "$AGENT_BIN" ]; then echo AGENT_MISSING; exit 1; fi;
nohup ${gatewayCmd} --port ${port} ...

This means recovery can validate one executable and run another command. If the binary only exists at binary_path and is not on PATH, recovery may still fail after passing the preflight check.

Please make validation and execution refer to the same executable.

Reasonable options:

  • For the default command, launch via the resolved "$AGENT_BIN" instead of a hard-coded/default command name.
  • For custom gateway_command, either validate the executable used by that command or intentionally bypass AGENT_BIN validation only when the custom command is the source of truth.

Acceptance criteria:

  • Default recovery uses the validated binary path.
  • Custom gateway_command behavior is explicit and tested.
  • Add/update coverage in src/lib/agent-runtime.test.ts.

4. Tighten persisted blueprint state loading

nemoclaw/src/blueprint/state.ts still treats any non-null object as Partial<NemoClawState>:

function isStatePatch(value: object | null): value is Partial<NemoClawState> {
  return value !== null && !Array.isArray(value);
}

Then loadState() merges that directly over blankState(). A malformed persisted file can leak bad runtime values like:

{
  "shieldsDown": "false",
  "updatedAt": {}
}

Please validate/whitelist known state fields before merging.

Acceptance criteria:

  • Incorrect field types are ignored or cause the persisted patch to be rejected.
  • Valid older partial state files still merge over blankState().
  • Add a malformed-state regression test in nemoclaw/src/blueprint/state.test.ts.

Review-body / Outside-diff Items Still Applying

These were raised in PR comments/review bodies and still appear to apply at ac361485.

5. Avoid [object Object] probe summaries

src/lib/http-probe.ts still does:

if (message) return `HTTP ${status}: ${String(message)}`;

For structured detail / details payloads, this can collapse useful diagnostics into [object Object].

Please add a safe formatter for ProbeErrorDetail:

  • strings: return directly
  • numbers/booleans/null: String(...)
  • arrays/objects: JSON.stringify(...)
  • stringify failure: safe fallback

Also add/update tests in src/lib/http-probe.test.ts.

6. Avoid undefined '<property>' in config validation errors

scripts/validate-configs.ts still interpolates err.message directly when formatting additionalProperty / unevaluatedProperty errors.

Please introduce a fallback first:

const message = err.message ?? "unknown error";

Then use message in all branches.

Acceptance criteria:

  • Missing AJV message never renders as undefined.
  • Existing error formatting remains unchanged when message exists.

7. Reject flags consumed as values in type-safety-hotspots CLI

scripts/type-safety-hotspots.ts still allows:

--root --json
--project --flag

to consume the next flag as a value, because requireDefined() only checks for undefined.

Please add a CLI value helper that rejects missing values and values starting with -, then use it for --root and --project.

Acceptance criteria:

  • --root --json throws a clear missing-value error for --root.
  • --project --json throws a clear missing-value error for --project.
  • Normal paths still work.

8. Do not use provider as endpoint fallback

nemoclaw/src/index.ts still has:

endpoint: endpoint ?? provider ?? "",

That can incorrectly populate endpoint with a provider name/value and bypass intended downstream defaults.

Please change it to:

endpoint: endpoint ?? "",

while keeping provider and model as their own fields.

9. Match getActiveGatewayName() implementation to its return type

src/nemoclaw.ts declares:

function getActiveGatewayName(output = ""): string | null

but still returns "" when there is no match.

Please return null instead:

return match ? match[1].trim() : null;

Docs / Generated Skills

10. Keep generated skill docs in sync

.agents/skills/nemoclaw-user-reference/references/commands.md was changed directly, but the source docs still differ in docs/reference/commands.md.

Please either:

  • revert the generated skill file change, or
  • update docs/reference/commands.md and regenerate with:
python3 scripts/docs-to-skills.py docs/ .agents/skills/ --prefix nemoclaw-user

Formatting / Validation

11. Run Prettier on changed files

Changed-file Prettier check still fails after ac361485; I’m seeing style issues across many changed root files.

Please run targeted formatting on changed files before final validation.

Suggested validation after fixes:

npm run build:cli
npm run typecheck:cli
npm run typecheck
cd nemoclaw && npm run build
cd ..
npx vitest run --project plugin
git diff --check origin/main...HEAD
git diff --name-only origin/main...HEAD | xargs npx prettier --check --ignore-unknown

I previously saw full CLI-project failures around installer fixtures/environment. I would not block on those unless they still reproduce on main or continue failing after the requested fixes above.

Summary

The PR is directionally good and the type-safety work is valuable, but I do not think it is merge-ready yet. The legacy manifest regression, invalid-action error regression, recovery command mismatch, persisted-state validation gap, and formatting failures should be addressed before merge.

@cv

cv commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator Author

I rechecked the PR at b10b72b5.

The functional issues I previously called out now look addressed:

  • Legacy rebuild manifests without blueprintDigest are accepted and normalized.
  • Invalid blueprint runner actions preserve the raw token and validate before blueprint loading.
  • Agent recovery now launches through the validated binary for the default path and validates custom commands separately.
  • Persisted blueprint state loading now whitelists/validates known fields before merging.
  • The remaining review-body items for http-probe, validate-configs, type-safety-hotspots, nemoclaw/src/index.ts, getActiveGatewayName(), and docs/generated skill sync look addressed.

Focused validation I ran:

npm run build:cli
npm run typecheck:cli
npm run typecheck
cd nemoclaw && npm run build
cd ..
npx vitest run --project cli test/snapshot.test.ts src/lib/agent-runtime.test.ts src/lib/http-probe.test.ts test/type-safety-hotspots.test.ts test/validate-configs-dangerous-hosts.test.ts test/rebuild-policy-presets.test.ts
npx vitest run --project plugin nemoclaw/src/blueprint/runner.test.ts nemoclaw/src/blueprint/state.test.ts nemoclaw/src/register.test.ts
git diff --check origin/main...HEAD

Those passed.

One remaining blocker: changed-file Prettier still fails.

git diff --name-only origin/main...HEAD | xargs npx prettier --check --ignore-unknown

This currently reports style issues in 61 changed files, including representative files such as:

  • scripts/bump-version.ts
  • scripts/check-coverage-ratchet.ts
  • scripts/validate-configs.ts
  • src/lib/http-probe.ts
  • src/lib/sandbox-state.ts
  • src/nemoclaw.ts
  • test/snapshot.test.ts
  • test/runner.test.ts
  • test/service-env.test.ts

Please run targeted formatting on the changed files, for example:

git diff --name-only origin/main...HEAD | xargs npx prettier --write --ignore-unknown

After that, I think the PR should be in much better shape for final validation.

@cv

cv commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator Author

The remaining formatting gate I previously called out is now clean.

At this point: this looks reasonable to merge once normal CI is green.

One caveat: because this is a broad repo-wide type-boundary cleanup, I would avoid stacking another large refactor on top immediately after merge. Even with the known issues addressed, the diff touches enough runtime guards, persisted-state parsing, tests, and formatting-sensitive files that it is worth treating this as a stabilize-for-a-beat merge. If anything shakes out after landing, it will be much easier to attribute and fix before layering on more sweeping changes.

@cv cv added the v0.0.25 label Apr 24, 2026
cv added 3 commits April 24, 2026 11:21
# Conflicts:
#	src/lib/onboard-session.ts
#	src/lib/onboard.ts
#	src/lib/runner.ts
#	src/lib/sandbox-config.ts
#	src/nemoclaw.ts
#	test/onboard.test.ts
#	test/runner.test.ts
#	test/secret-redaction.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (4)
scripts/bump-version.ts (1)

418-437: ⚠️ Potential issue | 🟠 Major

Handle prerelease/build semver in docs replacements.

parseArgs() accepts valid versions like 1.2.3-rc.1+build.5, but these regexes only match plain x.y.z or a single suffix. A bump from one of those accepted versions can leave stale docs links behind or make replaceCodeBlockLine() fail even though the CLI accepted the version.

Suggested fix
+const SEMVER_FRAGMENT =
+  String.raw`[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?`;
+
 function updateDocsVersionLinks(nextDocsPublicUrl: string): void {
   for (const filePath of VERSIONED_DOC_LINK_FILES) {
     const current = readText(filePath);
     const updated = current.replaceAll(
-      /https:\/\/docs\.nvidia\.com\/nemoclaw\/(?:latest|[0-9]+\.[0-9]+\.[0-9]+)\//g,
+      new RegExp(
+        String.raw`https:\/\/docs\.nvidia\.com\/nemoclaw\/(?:latest|${SEMVER_FRAGMENT})\/`,
+        "g",
+      ),
       `${nextDocsPublicUrl}/`,
     );
-    /^curl -fsSL https:\/\/www\.nvidia\.com\/nemoclaw\.sh \| bash(?: # v[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?)?$/m,
+    new RegExp(
+      String.raw`^curl -fsSL https:\/\/www\.nvidia\.com\/nemoclaw\.sh \| bash(?: # v${SEMVER_FRAGMENT})?$`,
+      "m",
+    ),

Also applies to: 446-465

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

In `@scripts/bump-version.ts` around lines 418 - 437, The regexes in
updateDocsVersionLinks and replaceAnyDocsUrl only match plain x.y.z; update them
to accept full semver with optional prerelease and build metadata by replacing
[0-9]+\.[0-9]+\.[0-9]+ (and the version-with-trailing-slash variant) with a
pattern like [0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?;
apply the same change to any other docs-replacement regexes in this file (e.g.,
the one used by replaceCodeBlockLine) so prerelease/build versions such as
1.2.3-rc.1+build.5 are matched and replaced correctly.
src/nemoclaw.ts (2)

523-530: ⚠️ Potential issue | 🟠 Major

Preserve existing recovery metadata instead of replacing it with this session stub.

This set() overwrites any already-seeded metadata for session.sandboxName with a much narrower object. When recoverRegistryFromLiveGateway() later walks all live sandboxes, upsertRecoveredSandbox() will update existing registry entries with that stripped-down payload, dropping fields like agent/gpuEnabled. It also ignores session.agent, so session-only recovery can resurrect a non-OpenClaw sandbox as agent: null.

Suggested fix
-  metadataByName.set(
-    session.sandboxName,
-    buildRecoveredSandboxEntry(session.sandboxName, {
-      model: session.model || null,
-      provider: session.provider || null,
-      nimContainer: session.nimContainer || null,
-      policyPresets: session.policyPresets || null,
-    }),
-  );
+  const existingMetadata = metadataByName.get(session.sandboxName) ?? {};
+  metadataByName.set(session.sandboxName, {
+    ...existingMetadata,
+    model: session.model ?? existingMetadata.model ?? null,
+    provider: session.provider ?? existingMetadata.provider ?? null,
+    nimContainer: session.nimContainer ?? existingMetadata.nimContainer ?? null,
+    agent: session.agent ?? existingMetadata.agent ?? null,
+    policyPresets: session.policyPresets ?? existingMetadata.policyPresets ?? null,
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nemoclaw.ts` around lines 523 - 530, The code currently calls
metadataByName.set(session.sandboxName, buildRecoveredSandboxEntry(...)) which
overwrites any previously-seeded metadata with a narrow session-only stub
(losing fields like agent/gpuEnabled and ignoring session.agent), so change the
logic to merge/update existing entry instead of replacing it: fetch existing =
metadataByName.get(session.sandboxName) (if any), merge existing with
buildRecoveredSandboxEntry(...) giving precedence to existing non-null fields
(or explicitly preserve fields like agent and gpuEnabled), then write the merged
object back with metadataByName.set; ensure this preserves seeded metadata
before recoverRegistryFromLiveGateway() and when upsertRecoveredSandbox() runs.

3911-3940: ⚠️ Potential issue | 🟠 Major

Validate required values for config flags before consuming them.

These loops currently accept the next flag token as a value. For example, config get --key --format yaml sets key="--format", config set --key --value foo sets key="--value", and rotate-token --from-env --from-stdin treats --from-stdin as an env-var name. These subcommands should reject missing or flag-like values the same way parseSnapshotCreateFlags() and shields down already do.

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

In `@src/nemoclaw.ts` around lines 3911 - 3940, The flag-parsing loops for the
config subcommands (inside the case handlers that iterate actionArgs)
incorrectly treat the next token as a value even when it's missing or another
flag; update the parsing in the cases that build configOpts, setOpts, and
tokenOpts so that when you see a value-taking flag (e.g., "--key", "--format",
"--value", "--from-env") you first check that actionArgs[i+1] exists and does
not startWith("--") before assigning and advancing i, otherwise surface an
error/return early (same style as parseSnapshotCreateFlags()/shields down)
instead of consuming a flag token as the value; boolean flags ("--restart",
"--from-stdin") remain handled as toggles and do not consume the next token.
nemoclaw/src/blueprint/runner.ts (1)

593-596: ⚠️ Potential issue | 🟡 Minor

Reject flag-shaped option values here.

requireValue() only checks bounds, so --profile --dry-run is parsed as profile="--dry-run" instead of surfacing a missing value. That turns a CLI usage error into a confusing follow-on failure.

Suggested fix
 function requireValue(flag: string, i: number): string {
-  if (i >= argv.length) throw new Error(`${flag} requires a value`);
-  return argv[i];
+  const value = argv[i];
+  if (i >= argv.length || value.startsWith("-")) {
+    throw new Error(`${flag} requires a value`);
+  }
+  return value;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nemoclaw/src/blueprint/runner.ts` around lines 593 - 596, The requireValue
helper only checks index bounds, so it treats flag-shaped tokens like
"--dry-run" as values; update requireValue(flag: string, i: number) to also
validate argv[i] does not begin with "-" (e.g., argv[i] === undefined ||
argv[i].startsWith("-") -> throw new Error(`${flag} requires a value`)). Locate
and change the requireValue function to perform this additional check (use the
existing argv and flag parameters and throw the same error message when a
flag-shaped token is encountered) so missing option values are reported
immediately.
🧹 Nitpick comments (6)
src/lib/usage-notice.ts (2)

99-107: Use the new property readers when constructing NoticeConfig.

Lines [99]-[107] still trust imported JSON shape directly for referenceUrl, body, and links. This bypasses the new runtime extraction helpers and can leak malformed values into rendering logic.

Proposed fix
 export function loadUsageNoticeConfig(): NoticeConfig {
-  const rawConfig: NoticeConfigSource = noticeConfig;
+  const rawConfig: object | null =
+    typeof noticeConfig === "object" && noticeConfig !== null ? noticeConfig : null;
   return {
-    version: rawConfig.version || "",
-    title: rawConfig.title || "",
-    referenceUrl: rawConfig.referenceUrl,
-    body: rawConfig.body,
-    links: rawConfig.links,
-    interactivePrompt: rawConfig.interactivePrompt || "",
+    version: readStringProperty(rawConfig, "version") ?? "",
+    title: readStringProperty(rawConfig, "title") ?? "",
+    referenceUrl: readStringProperty(rawConfig, "referenceUrl"),
+    body: readStringArrayProperty(rawConfig, "body"),
+    links: readLinksProperty(rawConfig, "links"),
+    interactivePrompt: readStringProperty(rawConfig, "interactivePrompt") ?? "",
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/usage-notice.ts` around lines 99 - 107, The code constructs a
NoticeConfig by reading fields directly from rawConfig (see
rawConfig.version/rawConfig.title/rawConfig.referenceUrl/rawConfig.body/rawConfig.links/interactivePrompt);
replace direct reads of referenceUrl, body and links with the new runtime
property readers (the helper functions introduced for extracting/validating
referenceUrl, body, and links) so that NoticeConfig uses the sanitized values
instead of rawConfig.referenceUrl/rawConfig.body/rawConfig.links; keep
version/title/interactivePrompt fallback logic as-is but call the readers for
the three properties when building the returned object in the function that
currently returns the NoticeConfig.

58-60: Avoid unchecked generic casting in JSON parsing.

Line [58] currently returns JSON.parse(text) as arbitrary T, which is still an unchecked type escape hatch. Prefer unknown and narrow at the call site.

Proposed fix
-function parseJson<T>(text: string): T {
+function parseJson(text: string): unknown {
   return JSON.parse(text);
 }

 export function hasAcceptedUsageNotice(version: string): boolean {
   try {
-    const saved = parseJson<{ acceptedVersion?: string }>(
-      fs.readFileSync(getUsageNoticeStateFile(), "utf8"),
-    );
-    return saved.acceptedVersion === version;
+    const saved = parseJson(fs.readFileSync(getUsageNoticeStateFile(), "utf8"));
+    const acceptedVersion =
+      typeof saved === "object" && saved !== null
+        ? readStringProperty(saved, "acceptedVersion")
+        : undefined;
+    return acceptedVersion === version;
   } catch {
     return false;
   }
 }

Also applies to: 112-115

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

In `@src/lib/usage-notice.ts` around lines 58 - 60, The parseJson<T> function
currently returns JSON.parse(text) cast to T which is an unchecked generic;
change its signature to return unknown (e.g., parseJson(text: string): unknown)
and remove the generic cast so parsing returns an unknown that must be narrowed
by callers; update all other places that used parseJson<T> (the other occurrence
at lines 112-115) to explicitly validate/transform the unknown into the expected
type (using type guards, schema validation, or explicit casts after checks) so
no unchecked generic escape hatch remains.
src/lib/agent-onboard.ts (1)

18-20: Narrow OnboardContext callback payload types instead of LooseObject.

startRecordedStep is now typed as arbitrary object, but its concrete implementation in src/lib/onboard.ts (Line 7189-7210) expects a small fixed key set. This weakens boundary checks and hides mismatches.

Proposed tightening
 type LooseScalar = string | number | boolean | null | undefined;
 type LooseValue = LooseScalar | LooseObject | LooseValue[];
 type LooseObject = { [key: string]: LooseValue };

+type AgentSetupStepUpdates = {
+  sandboxName?: string | null;
+  provider?: string | null;
+  model?: string | null;
+  policyPresets?: string[] | null;
+};
+
 export interface OnboardContext {
   step: (current: number, total: number, message: string) => void;
   runCaptureOpenshell: (args: string[], opts?: { ignoreError?: boolean }) => string | null;
   openshellShellCommand: (args: string[], options?: { openshellBinary?: string }) => string;
   openshellBinary: string;
   buildSandboxConfigSyncScript: (config: LooseObject) => string;
   writeSandboxConfigSyncFile: (script: string) => string;
   cleanupTempDir: (file: string, prefix: string) => void;
-  startRecordedStep: (stepName: string, updates: LooseObject) => void;
+  startRecordedStep: (stepName: string, updates: AgentSetupStepUpdates) => void;
   skippedStepMessage: (stepName: string, sandboxName: string) => void;
 }

Also applies to: 27-30

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

In `@src/lib/agent-onboard.ts` around lines 18 - 20, The callback payload types
for OnboardContext are too permissive (using LooseObject) while the concrete
implementation of startRecordedStep expects a fixed key set; define a specific
interface (e.g., StartRecordedStepPayload) describing the exact keys used by the
implementation and replace LooseObject with that interface in the OnboardContext
signature for startRecordedStep, and likewise create/narrow explicit payload
types for the other callbacks mentioned (lines 27-30) instead of reusing
LooseObject; update references to startRecordedStep and OnboardContext so the
type names match the expected fields from src/lib/onboard.ts (lines ~7189-7210).
test/e2e/brev-e2e.test.ts (1)

130-139: Apply requireInstanceName() consistently in command paths

You introduced a strong guard (requireInstanceName()), but ssh, brev create, and rsync still interpolate INSTANCE_NAME directly. Using the guard in these call paths keeps behavior deterministic and avoids accidental "undefined" targeting if preconditions change.

Proposed refactor
 function ssh(
   cmd: string,
   { timeout = 120_000, stream = false }: { timeout?: number; stream?: boolean } = {},
 ): string {
+  const instanceName = requireInstanceName();
   const escaped = cmd.replace(/'/g, "'\\''");
   const stdio = stream ? STREAM_STDIO : CAPTURE_STDIO;
   const result = execSync(
-    `ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR "${INSTANCE_NAME}" '${escaped}'`,
+    `ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR "${instanceName}" '${escaped}'`,
     { encoding: "utf-8", timeout, stdio },
   );
   return stream ? "" : result.trim();
 }
 function createBrevInstance(elapsed: () => string): void {
+  const instanceName = requireInstanceName();
   // ...
   execSync(
     `brev search cpu --min-vcpu ${BREV_MIN_VCPU} --min-ram ${BREV_MIN_RAM} --min-disk ${BREV_MIN_DISK} --provider ${BREV_PROVIDER} --sort price | ` +
-      `brev create ${INSTANCE_NAME} --startup-script @${setupScriptPath} --detached`,
+      `brev create ${instanceName} --startup-script @${setupScriptPath} --detached`,
     { encoding: "utf-8", timeout: 180_000, stdio: PIPE_INPUT_STDIO },
   );
-  execSync(
+  const instanceName = requireInstanceName();
+  execSync(
     `rsync -az --delete --exclude node_modules --exclude .git --exclude dist --exclude .venv "${REPO_DIR}/" "${INSTANCE_NAME}:${resolvedRemoteDir}/"`,
+    `rsync -az --delete --exclude node_modules --exclude .git --exclude dist --exclude .venv "${REPO_DIR}/" "${instanceName}:${resolvedRemoteDir}/"`,
     { encoding: "utf-8", timeout: 120_000 },
   );

Also applies to: 367-369, 412-413

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

In `@test/e2e/brev-e2e.test.ts` around lines 130 - 139, The ssh wrapper (function
ssh) and other call sites that interpolate INSTANCE_NAME directly (e.g., the ssh
command, brev create invocation, and rsync usage) must call
requireInstanceName() and use its return value instead of the raw INSTANCE_NAME
to ensure the guard is applied; update the ssh function to call const target =
requireInstanceName() and use target in the ssh command string, and similarly
replace direct INSTANCE_NAME uses in the brev create and rsync call sites with
requireInstanceName() to make behavior deterministic and prevent accidental
"undefined" targets.
src/lib/deploy.ts (1)

10-20: Keep the injected process APIs aligned with Node’s real types.

ExecLikeOptions plus spawnSync(...): void reintroduces a broad escape hatch here: misspelled options still type-check, and the injected spawnSync no longer exposes status/error/stdout. That works against the PR’s type-tightening goal. Prefer the Node option/result types directly, or a narrow Pick of them, on these injected dependencies.

Also applies to: 64-65

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

In `@src/lib/deploy.ts` around lines 10 - 20, The injected types are too loose:
replace the ad-hoc ExecLikeValue/ExecLikeOptions and the void-returning
spawnSync with Node’s real child_process types so misspelled options fail and
results expose status/error/stdout/stderr; specifically, change uses of
ExecLikeOptions to reference SpawnSyncOptions (or
SpawnSyncOptionsWithStringEncoding) and make the injected spawnSync have the
signature returning SpawnSyncReturns<Buffer | string> (or a narrowed Pick of
that type exposing status, error, stdout, stderr) so functions like spawnSync,
ExecLikeOptions, and ExecLikeValue use the built-in Node types rather than
generic object/string unions.
src/lib/sandbox-state.ts (1)

423-427: Consider using imported unlinkSync instead of require("node:fs").

The file already imports from node:fs (line 13-23) but uses require("node:fs").unlinkSync here. For consistency with the rest of the file, consider adding unlinkSync to the destructured imports.

♻️ Proposed fix

Add unlinkSync to the imports at the top of the file:

 import {
   chmodSync,
   existsSync,
   lstatSync,
   mkdirSync,
   readdirSync,
   readFileSync,
   readlinkSync,
   rmSync,
+  unlinkSync,
   writeFileSync,
 } from "node:fs";

Then use it directly:

-          try {
-            require("node:fs").unlinkSync(fullPath);
-          } catch {
+          try {
+            unlinkSync(fullPath);
+          } catch {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/sandbox-state.ts` around lines 423 - 427, Replace the inline
require("node:fs").unlinkSync usage with the imported unlinkSync for
consistency: add unlinkSync to the existing destructured imports from node:fs at
the top of the file, then change the try block that currently calls
require("node:fs").unlinkSync(fullPath) to call unlinkSync(fullPath) instead
(keeping the existing try/catch and fullPath variable intact).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/bump-version.ts`:
- Around line 390-404: The map callback in readDocsVersionsJson assumes each
parsed entry is an object and accesses entry.version directly, which throws on
null or non-object entries; update the map to first validate each element is a
non-null object (e.g. typeof entry === "object" && entry !== null &&
!Array.isArray(entry")) and reject otherwise with the existing "Each
docs/versions1.json entry must include a string version" error; then proceed to
read entry.version, entry.url and use buildDocsVersionUrl/version logic as
before so malformed entries fail with the intended validation message.

In `@src/lib/agent-runtime.ts`:
- Around line 61-65: binaryName can become an empty string when binaryPath ends
with a slash, producing a malformed defaultGatewayCommand; update the extraction
to guard against empty segments (e.g., use
binaryPath.split("/").filter(Boolean).pop() || "openclaw" or use
path.basename(binaryPath) || "openclaw") so defaultGatewayCommand,
configuredGatewayCommand, usesValidatedBinary, and customGatewayExecutable are
all derived from a non-empty binaryName; ensure agent.gateway_command?.trim()
still takes precedence for configuredGatewayCommand.

In `@src/lib/preflight.ts`:
- Line 186: The hardcoded rootless Docker config path causes misses; update the
const paths list used to detect daemon.json (the paths variable referenced by
dockerDefaultCgroupnsMode) to include a per-user home-based entry instead of
"/home/rootless/.config/docker/daemon.json" — e.g., construct the path with
os.homedir() (or process.env.HOME) and path.join(os.homedir(), ".config",
"docker", "daemon.json"), add that entry to paths, and ensure you import/require
the os/path modules if not already present.

In `@test/e2e/brev-e2e.test.ts`:
- Around line 380-387: The fallback check currently uses
lsOutput.includes(instanceName) which can match substrings; update the check in
the brev create fallback to perform exact instance name matching by parsing
lsOutput into lines (split on newlines), trimming each line, and comparing for
equality to requireInstanceName() or by using a anchored regex that escapes the
instanceName (e.g., ^escapedName$ with multiline flag) so only a full-line match
counts; update the conditional that references lsOutput and instanceName
accordingly to throw when no exact match is found.

---

Outside diff comments:
In `@nemoclaw/src/blueprint/runner.ts`:
- Around line 593-596: The requireValue helper only checks index bounds, so it
treats flag-shaped tokens like "--dry-run" as values; update requireValue(flag:
string, i: number) to also validate argv[i] does not begin with "-" (e.g.,
argv[i] === undefined || argv[i].startsWith("-") -> throw new Error(`${flag}
requires a value`)). Locate and change the requireValue function to perform this
additional check (use the existing argv and flag parameters and throw the same
error message when a flag-shaped token is encountered) so missing option values
are reported immediately.

In `@scripts/bump-version.ts`:
- Around line 418-437: The regexes in updateDocsVersionLinks and
replaceAnyDocsUrl only match plain x.y.z; update them to accept full semver with
optional prerelease and build metadata by replacing [0-9]+\.[0-9]+\.[0-9]+ (and
the version-with-trailing-slash variant) with a pattern like
[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?; apply the same
change to any other docs-replacement regexes in this file (e.g., the one used by
replaceCodeBlockLine) so prerelease/build versions such as 1.2.3-rc.1+build.5
are matched and replaced correctly.

In `@src/nemoclaw.ts`:
- Around line 523-530: The code currently calls
metadataByName.set(session.sandboxName, buildRecoveredSandboxEntry(...)) which
overwrites any previously-seeded metadata with a narrow session-only stub
(losing fields like agent/gpuEnabled and ignoring session.agent), so change the
logic to merge/update existing entry instead of replacing it: fetch existing =
metadataByName.get(session.sandboxName) (if any), merge existing with
buildRecoveredSandboxEntry(...) giving precedence to existing non-null fields
(or explicitly preserve fields like agent and gpuEnabled), then write the merged
object back with metadataByName.set; ensure this preserves seeded metadata
before recoverRegistryFromLiveGateway() and when upsertRecoveredSandbox() runs.
- Around line 3911-3940: The flag-parsing loops for the config subcommands
(inside the case handlers that iterate actionArgs) incorrectly treat the next
token as a value even when it's missing or another flag; update the parsing in
the cases that build configOpts, setOpts, and tokenOpts so that when you see a
value-taking flag (e.g., "--key", "--format", "--value", "--from-env") you first
check that actionArgs[i+1] exists and does not startWith("--") before assigning
and advancing i, otherwise surface an error/return early (same style as
parseSnapshotCreateFlags()/shields down) instead of consuming a flag token as
the value; boolean flags ("--restart", "--from-stdin") remain handled as toggles
and do not consume the next token.

---

Nitpick comments:
In `@src/lib/agent-onboard.ts`:
- Around line 18-20: The callback payload types for OnboardContext are too
permissive (using LooseObject) while the concrete implementation of
startRecordedStep expects a fixed key set; define a specific interface (e.g.,
StartRecordedStepPayload) describing the exact keys used by the implementation
and replace LooseObject with that interface in the OnboardContext signature for
startRecordedStep, and likewise create/narrow explicit payload types for the
other callbacks mentioned (lines 27-30) instead of reusing LooseObject; update
references to startRecordedStep and OnboardContext so the type names match the
expected fields from src/lib/onboard.ts (lines ~7189-7210).

In `@src/lib/deploy.ts`:
- Around line 10-20: The injected types are too loose: replace the ad-hoc
ExecLikeValue/ExecLikeOptions and the void-returning spawnSync with Node’s real
child_process types so misspelled options fail and results expose
status/error/stdout/stderr; specifically, change uses of ExecLikeOptions to
reference SpawnSyncOptions (or SpawnSyncOptionsWithStringEncoding) and make the
injected spawnSync have the signature returning SpawnSyncReturns<Buffer |
string> (or a narrowed Pick of that type exposing status, error, stdout, stderr)
so functions like spawnSync, ExecLikeOptions, and ExecLikeValue use the built-in
Node types rather than generic object/string unions.

In `@src/lib/sandbox-state.ts`:
- Around line 423-427: Replace the inline require("node:fs").unlinkSync usage
with the imported unlinkSync for consistency: add unlinkSync to the existing
destructured imports from node:fs at the top of the file, then change the try
block that currently calls require("node:fs").unlinkSync(fullPath) to call
unlinkSync(fullPath) instead (keeping the existing try/catch and fullPath
variable intact).

In `@src/lib/usage-notice.ts`:
- Around line 99-107: The code constructs a NoticeConfig by reading fields
directly from rawConfig (see
rawConfig.version/rawConfig.title/rawConfig.referenceUrl/rawConfig.body/rawConfig.links/interactivePrompt);
replace direct reads of referenceUrl, body and links with the new runtime
property readers (the helper functions introduced for extracting/validating
referenceUrl, body, and links) so that NoticeConfig uses the sanitized values
instead of rawConfig.referenceUrl/rawConfig.body/rawConfig.links; keep
version/title/interactivePrompt fallback logic as-is but call the readers for
the three properties when building the returned object in the function that
currently returns the NoticeConfig.
- Around line 58-60: The parseJson<T> function currently returns
JSON.parse(text) cast to T which is an unchecked generic; change its signature
to return unknown (e.g., parseJson(text: string): unknown) and remove the
generic cast so parsing returns an unknown that must be narrowed by callers;
update all other places that used parseJson<T> (the other occurrence at lines
112-115) to explicitly validate/transform the unknown into the expected type
(using type guards, schema validation, or explicit casts after checks) so no
unchecked generic escape hatch remains.

In `@test/e2e/brev-e2e.test.ts`:
- Around line 130-139: The ssh wrapper (function ssh) and other call sites that
interpolate INSTANCE_NAME directly (e.g., the ssh command, brev create
invocation, and rsync usage) must call requireInstanceName() and use its return
value instead of the raw INSTANCE_NAME to ensure the guard is applied; update
the ssh function to call const target = requireInstanceName() and use target in
the ssh command string, and similarly replace direct INSTANCE_NAME uses in the
brev create and rsync call sites with requireInstanceName() to make behavior
deterministic and prevent accidental "undefined" targets.
🪄 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: 6acdf078-4a6b-4382-8fb4-cbb2971ff250

📥 Commits

Reviewing files that changed from the base of the PR and between 45c531e and 958ef9e.

📒 Files selected for processing (78)
  • docs/reference/commands.md
  • nemoclaw/src/blueprint/runner.test.ts
  • nemoclaw/src/blueprint/runner.ts
  • nemoclaw/src/blueprint/state.test.ts
  • nemoclaw/src/blueprint/state.ts
  • nemoclaw/src/index.ts
  • nemoclaw/src/register.test.ts
  • scripts/bump-version.ts
  • scripts/check-coverage-ratchet.ts
  • scripts/type-safety-hotspots.ts
  • scripts/validate-configs.ts
  • src/lib/agent-onboard.ts
  • src/lib/agent-runtime.test.ts
  • src/lib/agent-runtime.ts
  • src/lib/credentials.ts
  • src/lib/dashboard-contract.test.ts
  • src/lib/dashboard-health.test.ts
  • src/lib/dashboard-recover.test.ts
  • src/lib/debug-command.test.ts
  • src/lib/deploy.ts
  • src/lib/http-probe.test.ts
  • src/lib/http-probe.ts
  • src/lib/messaging-conflict.test.ts
  • src/lib/onboard-session.ts
  • src/lib/onboard.ts
  • src/lib/policies.ts
  • src/lib/preflight.test.ts
  • src/lib/preflight.ts
  • src/lib/provider-models.ts
  • src/lib/registry.ts
  • src/lib/runner.ts
  • src/lib/sandbox-config.ts
  • src/lib/sandbox-create-stream.test.ts
  • src/lib/sandbox-create-stream.ts
  • src/lib/sandbox-session-state.ts
  • src/lib/sandbox-state.ts
  • src/lib/shields.ts
  • src/lib/skill-install.ts
  • src/lib/stale-dist-check.ts
  • src/lib/uninstall-command.test.ts
  • src/lib/uninstall-command.ts
  • src/lib/usage-notice.ts
  • src/nemoclaw.ts
  • test/cli.test.ts
  • test/config-set.test.ts
  • test/credential-rotation.test.ts
  • test/dns-proxy.test.ts
  • test/e2e/brev-e2e.test.ts
  • test/exec-approvals-path-regression.test.ts
  • test/gateway-liveness-probe.test.ts
  • test/gateway-state-reconcile-2276.test.ts
  • test/http-proxy-fix-sync.test.ts
  • test/install-npm-resolution.test.ts
  • test/install-preflight.test.ts
  • test/legacy-path-guard.test.ts
  • test/onboard-readiness.test.ts
  • test/onboard-selection.test.ts
  • test/onboard.test.ts
  • test/openclaw-data-ownership.test.ts
  • test/policies.test.ts
  • test/repro-2201.test.ts
  • test/runner.test.ts
  • test/runtime-shell.test.ts
  • test/sandbox-connect-inference.test.ts
  • test/sandbox-init.test.ts
  • test/secret-redaction.test.ts
  • test/security-c2-dockerfile-injection.test.ts
  • test/security-c4-manifest-traversal.test.ts
  • test/security-sandbox-tar-traversal.test.ts
  • test/service-env.test.ts
  • test/shields.test.ts
  • test/snapshot.test.ts
  • test/ssh-known-hosts.test.ts
  • test/type-safety-hotspots.test.ts
  • test/uninstall.test.ts
  • test/validate-blueprint.test.ts
  • test/validate-config-schemas.test.ts
  • test/wsl2-probe-timeout.test.ts
✅ Files skipped from review due to trivial changes (11)
  • src/lib/dashboard-health.test.ts
  • docs/reference/commands.md
  • src/lib/dashboard-recover.test.ts
  • test/uninstall.test.ts
  • test/install-npm-resolution.test.ts
  • src/lib/debug-command.test.ts
  • src/lib/sandbox-create-stream.test.ts
  • test/gateway-state-reconcile-2276.test.ts
  • test/repro-2201.test.ts
  • test/security-c2-dockerfile-injection.test.ts
  • test/wsl2-probe-timeout.test.ts
🚧 Files skipped from review as they are similar to previous changes (31)
  • nemoclaw/src/blueprint/state.ts
  • nemoclaw/src/blueprint/state.test.ts
  • test/legacy-path-guard.test.ts
  • test/runtime-shell.test.ts
  • test/dns-proxy.test.ts
  • test/http-proxy-fix-sync.test.ts
  • src/lib/stale-dist-check.ts
  • test/ssh-known-hosts.test.ts
  • test/exec-approvals-path-regression.test.ts
  • test/service-env.test.ts
  • src/lib/sandbox-session-state.ts
  • src/lib/agent-runtime.test.ts
  • test/sandbox-connect-inference.test.ts
  • src/lib/http-probe.test.ts
  • src/lib/uninstall-command.ts
  • src/lib/registry.ts
  • test/shields.test.ts
  • test/secret-redaction.test.ts
  • nemoclaw/src/blueprint/runner.test.ts
  • scripts/type-safety-hotspots.ts
  • test/onboard-readiness.test.ts
  • src/lib/messaging-conflict.test.ts
  • test/config-set.test.ts
  • test/credential-rotation.test.ts
  • scripts/check-coverage-ratchet.ts
  • src/lib/shields.ts
  • src/lib/preflight.test.ts
  • test/install-preflight.test.ts
  • nemoclaw/src/index.ts
  • test/validate-config-schemas.test.ts
  • test/security-c4-manifest-traversal.test.ts

Comment thread scripts/bump-version.ts
Comment on lines +390 to 404
const parsed = parseJson<Array<Partial<DocsVersionEntry>>>(readText(DOCS_VERSIONS_JSON));
if (!Array.isArray(parsed)) {
throw new Error("docs/versions1.json must contain an array");
}
return parsed.map((entry) => {
if (!entry || typeof entry !== "object") {
throw new Error("Invalid docs/versions1.json entry");
}
const candidate = entry as Partial<DocsVersionEntry>;
if (typeof candidate.version !== "string") {
const version = typeof entry.version === "string" ? entry.version : undefined;
if (!version) {
throw new Error("Each docs/versions1.json entry must include a string version");
}
const url = typeof entry.url === "string" ? entry.url : undefined;
return {
preferred: candidate.preferred === true ? true : undefined,
version: candidate.version,
url:
typeof candidate.url === "string" && candidate.url.length > 0
? candidate.url
: buildDocsVersionUrl(candidate.version),
preferred: entry.preferred === true ? true : undefined,
version,
url: url && url.length > 0 ? url : buildDocsVersionUrl(version),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reject non-object docs entries before reading version.

readDocsVersionsJson() now tries to validate malformed docs/versions1.json, but entries like null still blow up at entry.version with a raw property-access error. Guard each element as a non-array object first so this path fails with the intended validation message instead.

Suggested fix
     return parsed.map((entry) => {
+      if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
+        throw new Error("Each docs/versions1.json entry must be an object");
+      }
       const version = typeof entry.version === "string" ? entry.version : undefined;
       if (!version) {
         throw new Error("Each docs/versions1.json entry must include a string version");
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/bump-version.ts` around lines 390 - 404, The map callback in
readDocsVersionsJson assumes each parsed entry is an object and accesses
entry.version directly, which throws on null or non-object entries; update the
map to first validate each element is a non-null object (e.g. typeof entry ===
"object" && entry !== null && !Array.isArray(entry")) and reject otherwise with
the existing "Each docs/versions1.json entry must include a string version"
error; then proceed to read entry.version, entry.url and use
buildDocsVersionUrl/version logic as before so malformed entries fail with the
intended validation message.

Comment thread src/lib/agent-runtime.ts
Comment on lines +61 to +65
const binaryName = binaryPath.split("/").pop() ?? "openclaw";
const defaultGatewayCommand = `${binaryName} gateway run`;
const configuredGatewayCommand = agent.gateway_command?.trim() || defaultGatewayCommand;
const usesValidatedBinary = configuredGatewayCommand === defaultGatewayCommand;
const customGatewayExecutable = configuredGatewayCommand.split(/\s+/)[0] ?? binaryName;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Edge case: trailing slash in binary_path produces empty binaryName.

If binary_path ends with / (e.g., "/usr/local/bin/"), split("/").pop() returns "". The nullish coalescing operator (??) doesn't catch empty strings, so binaryName becomes "", leading to a malformed defaultGatewayCommand of " gateway run".

Consider using || instead, or filtering the split result:

Proposed fix
-  const binaryName = binaryPath.split("/").pop() ?? "openclaw";
+  const binaryName = binaryPath.split("/").pop() || "openclaw";
📝 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
const binaryName = binaryPath.split("/").pop() ?? "openclaw";
const defaultGatewayCommand = `${binaryName} gateway run`;
const configuredGatewayCommand = agent.gateway_command?.trim() || defaultGatewayCommand;
const usesValidatedBinary = configuredGatewayCommand === defaultGatewayCommand;
const customGatewayExecutable = configuredGatewayCommand.split(/\s+/)[0] ?? binaryName;
const binaryName = binaryPath.split("/").pop() || "openclaw";
const defaultGatewayCommand = `${binaryName} gateway run`;
const configuredGatewayCommand = agent.gateway_command?.trim() || defaultGatewayCommand;
const usesValidatedBinary = configuredGatewayCommand === defaultGatewayCommand;
const customGatewayExecutable = configuredGatewayCommand.split(/\s+/)[0] ?? binaryName;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/agent-runtime.ts` around lines 61 - 65, binaryName can become an
empty string when binaryPath ends with a slash, producing a malformed
defaultGatewayCommand; update the extraction to guard against empty segments
(e.g., use binaryPath.split("/").filter(Boolean).pop() || "openclaw" or use
path.basename(binaryPath) || "openclaw") so defaultGatewayCommand,
configuredGatewayCommand, usesValidatedBinary, and customGatewayExecutable are
all derived from a non-empty binaryName; ensure agent.gateway_command?.trim()
still takes precedence for configuredGatewayCommand.

Comment thread src/lib/preflight.ts
"/etc/docker/daemon.json",
"/home/rootless/.config/docker/daemon.json",
];
const paths = ["/etc/docker/daemon.json", "/home/rootless/.config/docker/daemon.json"];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a user-home-based rootless Docker config path

Line 186 hardcodes /home/rootless/.config/docker/daemon.json, which misses valid rootless configs for most users and can incorrectly return "unknown" for dockerDefaultCgroupnsMode.

Suggested fix
-  const paths = ["/etc/docker/daemon.json", "/home/rootless/.config/docker/daemon.json"];
+  const paths = ["/etc/docker/daemon.json", path.join(os.homedir(), ".config/docker/daemon.json")];
📝 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
const paths = ["/etc/docker/daemon.json", "/home/rootless/.config/docker/daemon.json"];
const paths = ["/etc/docker/daemon.json", path.join(os.homedir(), ".config/docker/daemon.json")];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/preflight.ts` at line 186, The hardcoded rootless Docker config path
causes misses; update the const paths list used to detect daemon.json (the paths
variable referenced by dockerDefaultCgroupnsMode) to include a per-user
home-based entry instead of "/home/rootless/.config/docker/daemon.json" — e.g.,
construct the path with os.homedir() (or process.env.HOME) and
path.join(os.homedir(), ".config", "docker", "daemon.json"), add that entry to
paths, and ensure you import/require the os/path modules if not already present.

Comment thread test/e2e/brev-e2e.test.ts
Comment on lines 380 to 387
const lsOutput = execSync(`brev ls 2>&1 || true`, { encoding: "utf-8", timeout: 30_000 });
if (!lsOutput.includes(INSTANCE_NAME)) {
const instanceName = requireInstanceName();
if (!lsOutput.includes(instanceName)) {
const createMessage = createErr instanceof Error ? createErr.message : String(createErr);
throw new Error(
`brev create failed and instance "${INSTANCE_NAME}" not found in brev ls. ` +
`Original error: ${createErr.message}`,
`brev create failed and instance "${instanceName}" not found in brev ls. ` +
`Original error: ${createMessage}`,
{ cause: createErr },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use exact instance matching in create fallback check

lsOutput.includes(instanceName) can false-positive on prefix/name overlap and let the flow continue even when the target instance was not created.

Proposed fix
-    const lsOutput = execSync(`brev ls 2>&1 || true`, { encoding: "utf-8", timeout: 30_000 });
-    const instanceName = requireInstanceName();
-    if (!lsOutput.includes(instanceName)) {
+    const instanceName = requireInstanceName();
+    if (!hasBrevInstance(instanceName)) {
       const createMessage = createErr instanceof Error ? createErr.message : String(createErr);
       throw new Error(
         `brev create failed and instance "${instanceName}" not found in brev ls. ` +
           `Original error: ${createMessage}`,
         { cause: createErr },
       );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/brev-e2e.test.ts` around lines 380 - 387, The fallback check
currently uses lsOutput.includes(instanceName) which can match substrings;
update the check in the brev create fallback to perform exact instance name
matching by parsing lsOutput into lines (split on newlines), trimming each line,
and comparing for equality to requireInstanceName() or by using a anchored regex
that escapes the instanceName (e.g., ^escapedName$ with multiline flag) so only
a full-line match counts; update the conditional that references lsOutput and
instanceName accordingly to throw when no exact match is found.

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

Good type-safety cleanup — the jump from 59 @ts-nocheck / 22 explicit any / 229 type assertions down to 0/0/0 is substantial. Architecture is sound: as casts replaced with runtime type guards throughout, error handling improved with instanceof Error checks, and the blueprint/config validation hardening is a net positive.

Approved with notes

Behavioral changes bundled in (not just types)

This PR bundles several real behavioral changes alongside the type tightening. They all look intentional and correct, but worth calling out for the commit record:

  1. buildRecoveryScript reworked (agent-runtime.ts): Now properly distinguishes default vs. custom gateway_command with separate validation paths (path-based [ -x ] vs. command -v for PATH lookup). This is an improvement — the old code used the agent binary path even for custom commands, which wouldn't validate correctly.

  2. Blueprint/config runtime validation: runner.ts, onboard/config.ts, sandbox-state.ts, migration-state.ts all now reject malformed data at load time instead of silently accepting via as casts. Good, but creates new failure modes for previously-tolerated corrupt files. loadState() now catches JSON parse errors and returns blankState() — resilience improvement.

  3. isErrnoException tightened (onboard-session.ts): Now requires instanceof Error in addition to having a code property. Low risk since Node.js always throws Error subclasses for fs operations, but non-Error thrown objects with code properties would no longer match.

  4. ws-proxy-fix.ts: Reflect.get/Reflect.set replaces direct symbol property access; null-host guard added (bugfix); servername now uses typeof === "string" instead of || (empty string "" now passes through instead of falling back to targetHost).

One question (non-blocking)

probeOpenShellInference (nemoclaw/src/index.ts): The old code was endpoint: parsed.endpoint ?? parsed.provider ?? "" — falling back to provider when endpoint was missing. The new code is endpoint: endpoint ?? "", dropping the provider fallback. Was this intentional? Using provider as an endpoint value seems like a bug in the original, so the removal looks correct, but wanted to confirm since it's a plugin-facing code path.

CI

All checks green except wsl-e2e still pending at time of review.

@jyaunches jyaunches self-assigned this Apr 24, 2026
@cv cv merged commit 660c7af into main Apr 24, 2026
19 checks passed
jyaunches added a commit that referenced this pull request Apr 24, 2026
…2447)

## Summary

Follow-up to PR #2422 (`refactor(types): tighten repo-wide type
boundaries`). Addresses the review warnings and suggestions from triage.

## Changes

### 1. Shared `isErrnoException` / `isPermissionError` —
`src/lib/errno.ts`

PR #2422 removed all `@ts-nocheck` markers, which required each module
to define its own `isErrnoException` + `ErrnoLike` type locally. This
resulted in 6 slightly different copies across `config-io`,
`credentials`, `http-probe`, `onboard`, `onboard-session`, and
`registry`.

This PR extracts a single canonical version into `src/lib/errno.ts`
that:
- Accepts `unknown` so callers never need an `instanceof Error` pre-cast
- Checks for both `code` and `errno` properties (covering all Node.js
error shapes)
- Includes a convenience `isPermissionError` for the common EACCES/EPERM
guard

This also simplifies many catch blocks that previously did `error
instanceof Error && isErrnoException(error)` — the redundant `instanceof
Error` check is now handled inside the shared function.

### 2. Shared JSON recursive types — `src/lib/json-types.ts`

PR #2422 introduced identical Scalar/Value/Object type triples in
multiple modules:
- `LooseScalar` / `LooseValue` / `LooseObject` in `onboard.ts` and
`agent-onboard.ts`
- `PolicyScalar` / `PolicyValue` / `PolicyObject` in `policies.ts`
- `SessionJsonPrimitive` / `SessionJsonValue` / `UnknownRecord` in
`onboard-session.ts`

This PR extracts canonical `JsonScalar` / `JsonValue` / `JsonObject`
types into `src/lib/json-types.ts`. Each module re-aliases them under
its domain names for readability.

> **Note:** The plugin types
(`PluginScalar`/`PluginValue`/`PluginRecord` in `nemoclaw/src/index.ts`)
are intentionally kept in-place because the plugin compiles separately
from the CLI and cannot share imports.

### 3. `Reflect.get` / `Reflect.set` convention comment

Adds a clarifying comment at the first usage site (`usage-notice.ts`)
explaining why `Reflect.get` is preferred over `as Record<…>` casts
throughout the codebase — it avoids widening the target type and
satisfies `no-unsafe-member-access` lint rules.

### 4. `ws-proxy-fix.ts` behavioral guard documentation

Documents the `!host` null guard in the Discord WebSocket tunnel path,
explaining that the previous code would have attempted a tunnel with an
undefined host (which would fail downstream anyway). The guard now
explicitly falls through to the original `https.request` unchanged.

## Type of Change

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

## Verification
- [x] `tsc -p tsconfig.cli.json --noEmit` passes
- [x] `npm test` passes (only pre-existing failures in install-preflight
and ssrf-parity)
- [x] Tests added for new `errno.ts` module (15 test cases)
- [x] No secrets, API keys, or credentials committed
- [ ] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only)

## AI Disclosure
- [x] AI-assisted — tool: pi coding agent

---
Signed-off-by: Jess Yaunches <jyaunches@nvidia.com>

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

* **Bug Fixes**
* Improved WebSocket proxy handling to avoid creating tunnels when the
request target host is undefined.

* **Tests**
  * Added tests validating errno/permission detection utilities.

* **Documentation**
  * Added explanatory comment clarifying property-access rationale.

* **Chores**
* Centralized JSON-type aliases and errno helpers for consistent error
handling across modules.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Jess Yaunches <jyaunches@nvidia.com>
prekshivyas added a commit that referenced this pull request Apr 24, 2026
Pre-push hook regenerates the compiled JS from the TypeScript source.
The TS was updated by a recent main-merge (PR #2422 adds the "attempted
tunnel with undefined host" comment) but the JS committed lagged,
blocking push on every commit.

No behavioural change — this is the exact output of tsc on the current
.ts source.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
ericksoa pushed a commit that referenced this pull request Apr 24, 2026
…2458)

## Summary

Tip of `main` currently fails `npm run typecheck:cli` with 8 errors in
`test/onboard.test.ts` — patterns introduced by #2130 (Slack token
validation tests) don't satisfy the `strict: true` setting introduced by
#2422. Every PR opened against main inherits these errors, which makes
the `checks` CI job red on unrelated PRs (e.g. #2454).

## Related

Follows from #2130 + #2422 interaction. No linked issue — local-only
breakage caught by CI on downstream PRs.

## Changes

`test/onboard.test.ts` — 8 annotations, zero behavior change:

- **4× `.pop()!`** — `Array.pop()` returns `string | undefined`. Each
site is immediately preceded by an `assert.equal(result.status, 0)` that
guarantees non-empty stdout, so the non-null assertion is safe.
- **3× `(c: { key: string }) => c.key === …`** — `saveCalls` entries
only read `.key`.
- **1× `(c: { name: string }) => c.name === …`** — `MESSAGING_CHANNELS`
entry lookup only reads `.name`.

The tests themselves still exercise the same cases; these changes are
purely to make the TypeScript compiler happy.

## Type of Change

- [x] Code change (feature, bug fix, or refactor)

## Verification

- [x] `npx tsc -p tsconfig.cli.json` — exits 0 (was 2 with 8 errors)
- [x] `npx prek run --all-files` passes
- [x] `npm test` passes
- [x] Tests added or updated for new or changed behavior
- [x] No secrets, API keys, or credentials committed

## AI Disclosure
- [x] AI-assisted — tool: Claude Code

---
Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>

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

## Summary by CodeRabbit

## Tests
* Improved reliability of JSON payload extraction in onboarding tests
with defensive null-checks before parsing.
* Enhanced type safety in test callbacks and iterators with explicit
TypeScript type annotations.

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

Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ericksoa pushed a commit that referenced this pull request Apr 24, 2026
## Summary

Prevents the class of regression that required #2458. Adds an
unconditional `typecheck:cli` step to the shared CI action so future
hook-filter drift can't hide a tsc error.

## Related

Follow-up to #2458 (which fixed the symptom — this addresses the root
cause).

## Root cause recap

- `.pre-commit-config.yaml` had `files: ^(bin|scripts)/` on the
`tsc-cli` hook.
- PRs that touched only `test/` or `src/` never triggered the check.
- #2130 (test-only Slack validation) slipped through that gap.
- #2422 later turned on `strict: true`, and the latent errors surfaced —
but for every downstream PR, not the PR that introduced them.
- Six open PRs ended up blocked on the same error cluster before anyone
noticed.

## Changes

`.github/actions/basic-checks/action.yaml` — new **Typecheck CLI + tests
(strict)** step running `npm run typecheck:cli` unconditionally.
Independent of prek hook configuration, so future filter drift can't
hide a tsc error from CI.

## Type of Change

- [x] Code change (feature, bug fix, or refactor)

## Verification

- [x] YAML parses
- [x] `npm run typecheck:cli` exits 0 on this branch (requires #2458
merged to main first)
- [x] Tests added or updated for new or changed behavior (N/A — infra
change, existing suite covers)
- [x] No secrets, API keys, or credentials committed

## AI Disclosure
- [x] AI-assisted — tool: Claude Code

---
Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>

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

## Summary by CodeRabbit

* **Chores**
* Enhanced continuous integration pipeline with additional TypeScript
typechecking to improve code quality assurance and detect type-related
issues earlier in the development process.

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

Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DemianHeyGen pushed a commit to DemianHeyGen/NemoClaw that referenced this pull request Apr 30, 2026
## Summary
This PR completes the repo-wide type-safety cleanup by removing the
remaining
`// @ts-nocheck` markers and tightening broad escape hatches across the
CLI,
plugin, blueprint, scripts, and tests. It replaces
`any`/`unknown`/assertion-
heavy paths with narrower local domains while keeping the full
correctness gate
green.

## Changes
- remove all remaining `// @ts-nocheck` markers and retire explicit
`any`,
`Record<string, unknown>`, type assertions, and non-null assertions that
were
  still inflating the hotspot report
- tighten high-churn CLI/onboarding paths in `src/lib/onboard.ts`,
`src/lib/onboard-session.ts`, `src/lib/runner.ts`,
`src/lib/credentials.ts`,
`src/lib/deploy.ts`, and related helpers with narrower
persisted-session,
  errno, config, and subprocess types
- tighten plugin and blueprint parsing paths in
  `nemoclaw/src/blueprint/runner.ts`, `snapshot.ts`, `state.ts`,
`onboard/config.ts`, and `commands/migration-state.ts` with concrete
source
  shapes around YAML and JSON boundaries
- update scripts and tests, including `scripts/type-safety-hotspots.ts`,
`scripts/bump-version.ts`, and many `test/*.ts` files, so the stricter
typing
  is exercised without weakening lint, TypeScript, or test gates
- reduce the tracked type-safety hotspots from
`59 @ts-nocheck / 22 explicit any / 83 record unknown / 229 type
assertions /
  50 non-null assertions / 251 unknowns` to `0 / 0 / 0 / 0 / 0 / 32`

## Type of Change

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

## Verification
- [x] `npx prek run --all-files` passes
- [x] `npm test` passes
- [x] Tests added or updated for new or changed behavior
- [x] No secrets, API keys, or credentials committed
- [ ] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only)
- [ ] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

## AI Disclosure
- [x] AI-assisted — tool: pi coding agent

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

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

* **Improvements**
* Stronger runtime validation and safer parsing across configs,
snapshots, and state—reduces crashes on corrupt or malformed files and
yields clearer error messages.
* Tighter input handling for plugins, onboarding, and runtime commands
to improve reliability and predictability.

* **Bug Fixes**
* More robust network/proxy and TLS handling during websocket/https
upgrades to avoid misrouted requests and fallback safely.

* **Tests**
* Expanded and hardened test coverage to prevent regressions and improve
stability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
DemianHeyGen pushed a commit to DemianHeyGen/NemoClaw that referenced this pull request Apr 30, 2026
…VIDIA#2447)

## Summary

Follow-up to PR NVIDIA#2422 (`refactor(types): tighten repo-wide type
boundaries`). Addresses the review warnings and suggestions from triage.

## Changes

### 1. Shared `isErrnoException` / `isPermissionError` —
`src/lib/errno.ts`

PR NVIDIA#2422 removed all `@ts-nocheck` markers, which required each module
to define its own `isErrnoException` + `ErrnoLike` type locally. This
resulted in 6 slightly different copies across `config-io`,
`credentials`, `http-probe`, `onboard`, `onboard-session`, and
`registry`.

This PR extracts a single canonical version into `src/lib/errno.ts`
that:
- Accepts `unknown` so callers never need an `instanceof Error` pre-cast
- Checks for both `code` and `errno` properties (covering all Node.js
error shapes)
- Includes a convenience `isPermissionError` for the common EACCES/EPERM
guard

This also simplifies many catch blocks that previously did `error
instanceof Error && isErrnoException(error)` — the redundant `instanceof
Error` check is now handled inside the shared function.

### 2. Shared JSON recursive types — `src/lib/json-types.ts`

PR NVIDIA#2422 introduced identical Scalar/Value/Object type triples in
multiple modules:
- `LooseScalar` / `LooseValue` / `LooseObject` in `onboard.ts` and
`agent-onboard.ts`
- `PolicyScalar` / `PolicyValue` / `PolicyObject` in `policies.ts`
- `SessionJsonPrimitive` / `SessionJsonValue` / `UnknownRecord` in
`onboard-session.ts`

This PR extracts canonical `JsonScalar` / `JsonValue` / `JsonObject`
types into `src/lib/json-types.ts`. Each module re-aliases them under
its domain names for readability.

> **Note:** The plugin types
(`PluginScalar`/`PluginValue`/`PluginRecord` in `nemoclaw/src/index.ts`)
are intentionally kept in-place because the plugin compiles separately
from the CLI and cannot share imports.

### 3. `Reflect.get` / `Reflect.set` convention comment

Adds a clarifying comment at the first usage site (`usage-notice.ts`)
explaining why `Reflect.get` is preferred over `as Record<…>` casts
throughout the codebase — it avoids widening the target type and
satisfies `no-unsafe-member-access` lint rules.

### 4. `ws-proxy-fix.ts` behavioral guard documentation

Documents the `!host` null guard in the Discord WebSocket tunnel path,
explaining that the previous code would have attempted a tunnel with an
undefined host (which would fail downstream anyway). The guard now
explicitly falls through to the original `https.request` unchanged.

## Type of Change

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

## Verification
- [x] `tsc -p tsconfig.cli.json --noEmit` passes
- [x] `npm test` passes (only pre-existing failures in install-preflight
and ssrf-parity)
- [x] Tests added for new `errno.ts` module (15 test cases)
- [x] No secrets, API keys, or credentials committed
- [ ] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only)

## AI Disclosure
- [x] AI-assisted — tool: pi coding agent

---
Signed-off-by: Jess Yaunches <jyaunches@nvidia.com>

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

* **Bug Fixes**
* Improved WebSocket proxy handling to avoid creating tunnels when the
request target host is undefined.

* **Tests**
  * Added tests validating errno/permission detection utilities.

* **Documentation**
  * Added explanatory comment clarifying property-access rationale.

* **Chores**
* Centralized JSON-type aliases and errno helpers for consistent error
handling across modules.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Jess Yaunches <jyaunches@nvidia.com>
DemianHeyGen pushed a commit to DemianHeyGen/NemoClaw that referenced this pull request Apr 30, 2026
…VIDIA#2458)

## Summary

Tip of `main` currently fails `npm run typecheck:cli` with 8 errors in
`test/onboard.test.ts` — patterns introduced by NVIDIA#2130 (Slack token
validation tests) don't satisfy the `strict: true` setting introduced by
NVIDIA#2422. Every PR opened against main inherits these errors, which makes
the `checks` CI job red on unrelated PRs (e.g. NVIDIA#2454).

## Related

Follows from NVIDIA#2130 + NVIDIA#2422 interaction. No linked issue — local-only
breakage caught by CI on downstream PRs.

## Changes

`test/onboard.test.ts` — 8 annotations, zero behavior change:

- **4× `.pop()!`** — `Array.pop()` returns `string | undefined`. Each
site is immediately preceded by an `assert.equal(result.status, 0)` that
guarantees non-empty stdout, so the non-null assertion is safe.
- **3× `(c: { key: string }) => c.key === …`** — `saveCalls` entries
only read `.key`.
- **1× `(c: { name: string }) => c.name === …`** — `MESSAGING_CHANNELS`
entry lookup only reads `.name`.

The tests themselves still exercise the same cases; these changes are
purely to make the TypeScript compiler happy.

## Type of Change

- [x] Code change (feature, bug fix, or refactor)

## Verification

- [x] `npx tsc -p tsconfig.cli.json` — exits 0 (was 2 with 8 errors)
- [x] `npx prek run --all-files` passes
- [x] `npm test` passes
- [x] Tests added or updated for new or changed behavior
- [x] No secrets, API keys, or credentials committed

## AI Disclosure
- [x] AI-assisted — tool: Claude Code

---
Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>

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

## Summary by CodeRabbit

## Tests
* Improved reliability of JSON payload extraction in onboarding tests
with defensive null-checks before parsing.
* Enhanced type safety in test callbacks and iterators with explicit
TypeScript type annotations.

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

Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DemianHeyGen pushed a commit to DemianHeyGen/NemoClaw that referenced this pull request Apr 30, 2026
## Summary

Prevents the class of regression that required NVIDIA#2458. Adds an
unconditional `typecheck:cli` step to the shared CI action so future
hook-filter drift can't hide a tsc error.

## Related

Follow-up to NVIDIA#2458 (which fixed the symptom — this addresses the root
cause).

## Root cause recap

- `.pre-commit-config.yaml` had `files: ^(bin|scripts)/` on the
`tsc-cli` hook.
- PRs that touched only `test/` or `src/` never triggered the check.
- NVIDIA#2130 (test-only Slack validation) slipped through that gap.
- NVIDIA#2422 later turned on `strict: true`, and the latent errors surfaced —
but for every downstream PR, not the PR that introduced them.
- Six open PRs ended up blocked on the same error cluster before anyone
noticed.

## Changes

`.github/actions/basic-checks/action.yaml` — new **Typecheck CLI + tests
(strict)** step running `npm run typecheck:cli` unconditionally.
Independent of prek hook configuration, so future filter drift can't
hide a tsc error from CI.

## Type of Change

- [x] Code change (feature, bug fix, or refactor)

## Verification

- [x] YAML parses
- [x] `npm run typecheck:cli` exits 0 on this branch (requires NVIDIA#2458
merged to main first)
- [x] Tests added or updated for new or changed behavior (N/A — infra
change, existing suite covers)
- [x] No secrets, API keys, or credentials committed

## AI Disclosure
- [x] AI-assisted — tool: Claude Code

---
Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>

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

## Summary by CodeRabbit

* **Chores**
* Enhanced continuous integration pipeline with additional TypeScript
typechecking to improve code quality assurance and detect type-related
issues earlier in the development process.

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

Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ksapru pushed a commit to ksapru/NemoClaw that referenced this pull request May 12, 2026
This PR completes the repo-wide type-safety cleanup by removing the
remaining
`// @ts-nocheck` markers and tightening broad escape hatches across the
CLI,
plugin, blueprint, scripts, and tests. It replaces
`any`/`unknown`/assertion-
heavy paths with narrower local domains while keeping the full
correctness gate
green.

- remove all remaining `// @ts-nocheck` markers and retire explicit
`any`,
`Record<string, unknown>`, type assertions, and non-null assertions that
were
  still inflating the hotspot report
- tighten high-churn CLI/onboarding paths in `src/lib/onboard.ts`,
`src/lib/onboard-session.ts`, `src/lib/runner.ts`,
`src/lib/credentials.ts`,
`src/lib/deploy.ts`, and related helpers with narrower
persisted-session,
  errno, config, and subprocess types
- tighten plugin and blueprint parsing paths in
  `nemoclaw/src/blueprint/runner.ts`, `snapshot.ts`, `state.ts`,
`onboard/config.ts`, and `commands/migration-state.ts` with concrete
source
  shapes around YAML and JSON boundaries
- update scripts and tests, including `scripts/type-safety-hotspots.ts`,
`scripts/bump-version.ts`, and many `test/*.ts` files, so the stricter
typing
  is exercised without weakening lint, TypeScript, or test gates
- reduce the tracked type-safety hotspots from
`59 @ts-nocheck / 22 explicit any / 83 record unknown / 229 type
assertions /
  50 non-null assertions / 251 unknowns` to `0 / 0 / 0 / 0 / 0 / 32`

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

- [x] `npx prek run --all-files` passes
- [x] `npm test` passes
- [x] Tests added or updated for new or changed behavior
- [x] No secrets, API keys, or credentials committed
- [ ] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only)
- [ ] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

- [x] AI-assisted — tool: pi coding agent

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

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

* **Improvements**
* Stronger runtime validation and safer parsing across configs,
snapshots, and state—reduces crashes on corrupt or malformed files and
yields clearer error messages.
* Tighter input handling for plugins, onboarding, and runtime commands
to improve reliability and predictability.

* **Bug Fixes**
* More robust network/proxy and TLS handling during websocket/https
upgrades to avoid misrouted requests and fallback safely.

* **Tests**
* Expanded and hardened test coverage to prevent regressions and improve
stability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
ksapru pushed a commit to ksapru/NemoClaw that referenced this pull request May 12, 2026
…VIDIA#2447)

## Summary

Follow-up to PR NVIDIA#2422 (`refactor(types): tighten repo-wide type
boundaries`). Addresses the review warnings and suggestions from triage.

## Changes

### 1. Shared `isErrnoException` / `isPermissionError` —
`src/lib/errno.ts`

PR NVIDIA#2422 removed all `@ts-nocheck` markers, which required each module
to define its own `isErrnoException` + `ErrnoLike` type locally. This
resulted in 6 slightly different copies across `config-io`,
`credentials`, `http-probe`, `onboard`, `onboard-session`, and
`registry`.

This PR extracts a single canonical version into `src/lib/errno.ts`
that:
- Accepts `unknown` so callers never need an `instanceof Error` pre-cast
- Checks for both `code` and `errno` properties (covering all Node.js
error shapes)
- Includes a convenience `isPermissionError` for the common EACCES/EPERM
guard

This also simplifies many catch blocks that previously did `error
instanceof Error && isErrnoException(error)` — the redundant `instanceof
Error` check is now handled inside the shared function.

### 2. Shared JSON recursive types — `src/lib/json-types.ts`

PR NVIDIA#2422 introduced identical Scalar/Value/Object type triples in
multiple modules:
- `LooseScalar` / `LooseValue` / `LooseObject` in `onboard.ts` and
`agent-onboard.ts`
- `PolicyScalar` / `PolicyValue` / `PolicyObject` in `policies.ts`
- `SessionJsonPrimitive` / `SessionJsonValue` / `UnknownRecord` in
`onboard-session.ts`

This PR extracts canonical `JsonScalar` / `JsonValue` / `JsonObject`
types into `src/lib/json-types.ts`. Each module re-aliases them under
its domain names for readability.

> **Note:** The plugin types
(`PluginScalar`/`PluginValue`/`PluginRecord` in `nemoclaw/src/index.ts`)
are intentionally kept in-place because the plugin compiles separately
from the CLI and cannot share imports.

### 3. `Reflect.get` / `Reflect.set` convention comment

Adds a clarifying comment at the first usage site (`usage-notice.ts`)
explaining why `Reflect.get` is preferred over `as Record<…>` casts
throughout the codebase — it avoids widening the target type and
satisfies `no-unsafe-member-access` lint rules.

### 4. `ws-proxy-fix.ts` behavioral guard documentation

Documents the `!host` null guard in the Discord WebSocket tunnel path,
explaining that the previous code would have attempted a tunnel with an
undefined host (which would fail downstream anyway). The guard now
explicitly falls through to the original `https.request` unchanged.

## Type of Change

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

## Verification
- [x] `tsc -p tsconfig.cli.json --noEmit` passes
- [x] `npm test` passes (only pre-existing failures in install-preflight
and ssrf-parity)
- [x] Tests added for new `errno.ts` module (15 test cases)
- [x] No secrets, API keys, or credentials committed
- [ ] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only)

## AI Disclosure
- [x] AI-assisted — tool: pi coding agent

---
Signed-off-by: Jess Yaunches <jyaunches@nvidia.com>

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

* **Bug Fixes**
* Improved WebSocket proxy handling to avoid creating tunnels when the
request target host is undefined.

* **Tests**
  * Added tests validating errno/permission detection utilities.

* **Documentation**
  * Added explanatory comment clarifying property-access rationale.

* **Chores**
* Centralized JSON-type aliases and errno helpers for consistent error
handling across modules.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Jess Yaunches <jyaunches@nvidia.com>
ksapru pushed a commit to ksapru/NemoClaw that referenced this pull request May 12, 2026
…VIDIA#2458)

## Summary

Tip of `main` currently fails `npm run typecheck:cli` with 8 errors in
`test/onboard.test.ts` — patterns introduced by NVIDIA#2130 (Slack token
validation tests) don't satisfy the `strict: true` setting introduced by
NVIDIA#2422. Every PR opened against main inherits these errors, which makes
the `checks` CI job red on unrelated PRs (e.g. NVIDIA#2454).

## Related

Follows from NVIDIA#2130 + NVIDIA#2422 interaction. No linked issue — local-only
breakage caught by CI on downstream PRs.

## Changes

`test/onboard.test.ts` — 8 annotations, zero behavior change:

- **4× `.pop()!`** — `Array.pop()` returns `string | undefined`. Each
site is immediately preceded by an `assert.equal(result.status, 0)` that
guarantees non-empty stdout, so the non-null assertion is safe.
- **3× `(c: { key: string }) => c.key === …`** — `saveCalls` entries
only read `.key`.
- **1× `(c: { name: string }) => c.name === …`** — `MESSAGING_CHANNELS`
entry lookup only reads `.name`.

The tests themselves still exercise the same cases; these changes are
purely to make the TypeScript compiler happy.

## Type of Change

- [x] Code change (feature, bug fix, or refactor)

## Verification

- [x] `npx tsc -p tsconfig.cli.json` — exits 0 (was 2 with 8 errors)
- [x] `npx prek run --all-files` passes
- [x] `npm test` passes
- [x] Tests added or updated for new or changed behavior
- [x] No secrets, API keys, or credentials committed

## AI Disclosure
- [x] AI-assisted — tool: Claude Code

---
Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>

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

## Summary by CodeRabbit

## Tests
* Improved reliability of JSON payload extraction in onboarding tests
with defensive null-checks before parsing.
* Enhanced type safety in test callbacks and iterators with explicit
TypeScript type annotations.

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

Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ksapru pushed a commit to ksapru/NemoClaw that referenced this pull request May 12, 2026
## Summary

Prevents the class of regression that required NVIDIA#2458. Adds an
unconditional `typecheck:cli` step to the shared CI action so future
hook-filter drift can't hide a tsc error.

## Related

Follow-up to NVIDIA#2458 (which fixed the symptom — this addresses the root
cause).

## Root cause recap

- `.pre-commit-config.yaml` had `files: ^(bin|scripts)/` on the
`tsc-cli` hook.
- PRs that touched only `test/` or `src/` never triggered the check.
- NVIDIA#2130 (test-only Slack validation) slipped through that gap.
- NVIDIA#2422 later turned on `strict: true`, and the latent errors surfaced —
but for every downstream PR, not the PR that introduced them.
- Six open PRs ended up blocked on the same error cluster before anyone
noticed.

## Changes

`.github/actions/basic-checks/action.yaml` — new **Typecheck CLI + tests
(strict)** step running `npm run typecheck:cli` unconditionally.
Independent of prek hook configuration, so future filter drift can't
hide a tsc error from CI.

## Type of Change

- [x] Code change (feature, bug fix, or refactor)

## Verification

- [x] YAML parses
- [x] `npm run typecheck:cli` exits 0 on this branch (requires NVIDIA#2458
merged to main first)
- [x] Tests added or updated for new or changed behavior (N/A — infra
change, existing suite covers)
- [x] No secrets, API keys, or credentials committed

## AI Disclosure
- [x] AI-assisted — tool: Claude Code

---
Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>

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

## Summary by CodeRabbit

* **Chores**
* Enhanced continuous integration pipeline with additional TypeScript
typechecking to improve code quality assurance and detect type-related
issues earlier in the development process.

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

Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cv cv deleted the autoresearch/finalize/01-consolidated-type-precision branch May 27, 2026 21:16
@wscurran wscurran added the refactor PR restructures code without intended behavior change label Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor PR restructures code without intended behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants