Skip to content

perf(test): run model router onboarding tests in-process - #6411

Merged
cv merged 4 commits into
mainfrom
codex/perf-in-process-onboard-model-router
Jul 7, 2026
Merged

perf(test): run model router onboarding tests in-process#6411
cv merged 4 commits into
mainfrom
codex/perf-in-process-onboard-model-router

Conversation

@cv

@cv cv commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replace five unit-shaped Model Router onboarding child-process tests with direct typed boundaries while retaining a lightweight real process contract. Extract managed-command provisioning so route, installation, reuse, fingerprint, and launch behavior can be tested without repeatedly loading the full onboarding graph or waiting on real health intervals.

Related Issue

Refs #6245

Changes

  • Extract Model Router command discovery, managed-venv installation, and source fingerprinting into a dependency-light provisioner while preserving the existing model-router.ts exports.
  • Give startModelRouter an immutable per-call dependency seam with unchanged production defaults.
  • Replace five source-loader child drivers with eight direct tests covering routed-provider wiring, managed command installation/reuse/refresh, fallback fingerprints, production adapter composition, and real proxy launch arguments.
  • Retain one lightweight real fake-router process contract for config generation, detached launch, credential environment filtering, PID liveness, and cleanup.
  • Reduce the matched CI file result from 14,005.160ms on current main to 404.958ms on the final head (97.11% lower, 34.58× faster); collection plus tests fell from 14,054.999ms to 961.167ms (93.16% lower).

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)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: internal module extraction and test dependency seams only; Model Router provider selection, credentials, ports, managed virtual environment, startup behavior, and failure semantics are unchanged.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: independent final-diff review found no actionable correctness, security, credential-handling, typing, or test-isolation findings; the retained process contract verifies filtered credential delivery and cleanup.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Verification

  • PR description includes the DCO sign-off declaration and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run check:diff passed when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — The target file passes 8/8 locally and on final-head CI; related Model Router/routed-provider suites and 2/2 Model Router E2E-support tests also passed.
  • Required live E2E targets passed — model-router-provider-routed-inference and cloud-onboard both passed in run 28883447759; the final follow-up changes only test-log parsing.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result:
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run 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)

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

Summary by CodeRabbit

  • New Features
    • Added managed Model Router command provisioning that reuses an existing managed venv install when it matches the current router source, and provisions it when needed.
    • Refactored router startup to support dependency-injected health checks and proxy spawning.
  • Bug Fixes
    • Improved “currentness” detection using source fingerprints, with a safe fallback when git-based fingerprinting isn’t available.
    • More consistent readiness, termination, and retry behavior during startup.
  • Tests
    • Reworked Model Router onboarding tests to run in-process with a command harness, plus new process/log test helpers and fixtures.

@cv cv self-assigned this Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Model Router command provisioning is extracted into a new module, router startup now uses injected dependencies, and the onboarding tests were rewritten to run in-process with new fixture and process helpers.

Changes

Model Router provisioner extraction and DI refactor

Layer / File(s) Summary
Command provisioner types and helpers
src/lib/onboard/model-router-command.ts
Defines fingerprint filename and ignored directories, shared command and dependency types, path helpers, source hashing, fingerprint read/write helpers, source fingerprint selection, and the provisioner factory shape.
Install and freshness checks
src/lib/onboard/model-router-command.ts
Validates managed-command currency, initializes submodules when needed, installs the router command into the venv, resolves a host command fallback, and selects between refresh, reuse, discovery, and install paths.
Router delegation and startup injection
src/lib/onboard/model-router.ts
Re-exports the provisioner API, removes local provisioning code, adds startup dependency injection, and rewires startup, health polling, and termination through injected process helpers.
In-process test harness and coverage
test/onboard-model-router.test.ts, test/support/model-router-process-test-helpers.ts
Replaces script-based tests with direct Vitest harnesses, adds process and launch-log helpers, and rewrites coverage for provisioning, managed-command selection, host fallback, refresh behavior, and router startup.

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

Possibly related PRs

  • NVIDIA/NemoClaw#3653: Both PRs touch onboarding/router command resolution paths and related startup wiring.

Suggested labels: refactor

Suggested reviewers: jyaunches, ericksoa, prekshivyas

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: moving Model Router onboarding tests in-process.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/perf-in-process-onboard-model-router

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

@github-code-quality

github-code-quality Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in the codex/perf-in-proces... branch is 96%. Coverage data for the main branch is not yet available.

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

TypeScript / code-coverage/cli

The overall coverage in the codex/perf-in-proces... branch is 75%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main codex/perf-in-proces... 2955d81 +/-
src/lib/shields...nsition-lock.ts 85%
src/lib/onboard/preflight.ts 83%
src/lib/actions...all/run-plan.ts 81%
src/lib/state/o...oard-session.ts 80%
src/lib/actions...licy-channel.ts 79%
src/lib/state/sandbox.ts 75%
src/lib/onboard...er-gpu-patch.ts 69%
src/lib/policy/index.ts 66%
src/lib/shields/index.ts 61%
src/lib/onboard.ts 28%

Updated July 07, 2026 17:24 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: model-router-provider-routed-inference, cloud-onboard
Optional E2E: cloud-onboard, inference-routing

Dispatch hint: model-router-provider-routed-inference

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • model-router-provider-routed-inference (medium): This is the focused existing live E2E for the changed boundary: real provider-routed nemoclaw onboard --fresh, managed host Model Router health, credential staging for the router, and sandbox https://inference.local routed completion. The PR changes exactly the Model Router command provisioning/startup path that this job validates.
  • cloud-onboard (high): Changed onboard, trace timing, scorecard, or E2E workflow code can affect cloud onboard wall-clock behavior and should refresh the trusted cloud-onboard trace timing signal.

Optional E2E

  • cloud-onboard (medium): Useful broad confidence for source-install hosted onboarding and sandbox creation after changes under src/lib/onboard, but the touched runtime code is specific to the routed Model Router path rather than the normal hosted cloud provider path.
  • inference-routing (medium): Adjacent PR-safe coverage for inference provider routing, invalid/unreachable endpoint handling, and credential isolation. It does not exercise the managed Model Router process, so it is supplementary rather than a substitute for model-router-provider-routed-inference.

New E2E recommendations

  • None.

Dispatch hint

  • Workflow: e2e.yaml
  • jobs input: model-router-provider-routed-inference

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Recommendation

Required E2E targets: model-router-provider-routed-inference
Optional E2E targets: None

Dispatch required E2E targets:

  • gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=model-router-provider-routed-inference

Workflow run

Full E2E target advisor summary

E2E Target Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E targets

  • model-router-provider-routed-inference: The PR refactors Model Router command provisioning and startup in src/lib/onboard/model-router*.ts, which directly affects routed onboarding, managed model-router installation, proxy startup, host health, and sandbox inference.local behavior. The wired free-standing e2e.yaml job exercises that live Model Router provider-routed contract.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=model-router-provider-routed-inference

Optional E2E targets

  • None.

Relevant changed files

  • src/lib/onboard/model-router-command.ts
  • src/lib/onboard/model-router.ts

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor (Nemotron Ultra) — Blocked

Merge posture: Do not merge until addressed
Primary next action: Fix PRA-5: hashModelRouterSourceTree lacks routerDir containment validation; then add or justify PRA-T1.
Open items: 4 required · 9 warnings · 2 suggestions · 8 test follow-ups
Since last review: 0 prior items resolved · 12 still apply · 2 new items found

Action checklist

  • PRA-5 Fix: hashModelRouterSourceTree lacks routerDir containment validation in src/lib/onboard/model-router-command.ts:85
  • PRA-6 Fix: Symlink targets hashed without containment check in source fingerprinting in src/lib/onboard/model-router-command.ts:110
  • PRA-7 Fix: Git submodule initialization trust assumption undocumented and errors silenced in src/lib/onboard/model-router-command.ts:185
  • PRA-8 Fix: Missing negative security tests for enabled attack vectors in test/onboard-model-router.test.ts:1
  • PRA-1 Resolve or justify: Source-of-truth review needed: src/lib/onboard/model-router-command.ts:92 (hashDirectory readdirSync catch)
  • PRA-2 Resolve or justify: Source-of-truth review needed: src/lib/onboard/model-router-command.ts:113 (symlink readlinkSync catch)
  • PRA-3 Resolve or justify: Source-of-truth review needed: src/lib/onboard/model-router-command.ts:134 (readModelRouterInstalledFingerprint catch-all)
  • PRA-4 Resolve or justify: Source-of-truth review needed: src/lib/onboard/model-router-command.ts:188 (git submodule update ignoreError: true)
  • PRA-9 Resolve or justify: Shell string execution in resolveHostCommandPath in src/lib/onboard/model-router-command.ts:245
  • PRA-10 Resolve or justify: Source-of-truth review: hashModelRouterSourceTree/hashDirectory silent catch on readdirSync in src/lib/onboard/model-router-command.ts:92
  • PRA-11 Resolve or justify: Source-of-truth review: hashModelRouterSourceTree silent catch on symlink read in src/lib/onboard/model-router-command.ts:113
  • PRA-12 Resolve or justify: Source-of-truth review: readModelRouterInstalledFingerprint silent catch-all in src/lib/onboard/model-router-command.ts:134
  • PRA-13 Resolve or justify: Source-of-truth review: initializeModelRouterSubmodule git submodule update with ignoreError: true in src/lib/onboard/model-router-command.ts:188
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Runtime validation
  • PRA-T3 Add or justify test follow-up: Runtime validation
  • PRA-T4 Add or justify test follow-up: Runtime validation
  • PRA-T5 Add or justify test follow-up: Runtime validation
  • PRA-T6 Add or justify test follow-up: Missing negative security tests for enabled attack vectors
  • PRA-T7 Add or justify test follow-up: Acceptance clause
  • PRA-T8 Add or justify test follow-up: Acceptance clause
  • PRA-14 In-scope improvement: getProviderKey DI default duplicated from old inline usage in src/lib/onboard/model-router.ts:211
  • PRA-15 In-scope improvement: New test helper file adds infrastructure that could be simplified in test/support/model-router-process-test-helpers.ts:1

Findings index

ID Severity Category Location Required action
PRA-1 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-2 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-3 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-4 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-5 Required security src/lib/onboard/model-router-command.ts:85 Add validation at function entry: assert path.isAbsolute(routerDir) and resolve to realpath via fs.realpathSync before walking. Throw if resolved path escapes expected root (e.g., not under paths.rootDir).
PRA-6 Required security src/lib/onboard/model-router-command.ts:110 Validate symlink targets resolve within routerDir: use fs.realpathSync on entryPath and check it starts with realpath(routerDir). Skip or error on external symlinks.
PRA-7 Required security src/lib/onboard/model-router-command.ts:185 Add code comment documenting that .gitmodules is trusted source-controlled configuration and submodule URLs must be reviewed in PRs. Consider removing ignoreError: true to surface clone failures, or at minimum log a warning on failure.
PRA-8 Required tests test/onboard-model-router.test.ts:1 Add security-focused test cases to createCommandHarness tests: (1) routerDir as symlink to sensitive area, (2) symlink in source tree pointing outside, (3) verify pip install command uses argv array not shell string, (4) verify error messages don't leak credentials, (5) verify submodule init failure handling.
PRA-9 Resolve/justify security src/lib/onboard/model-router-command.ts:245 Replace with a non-shell implementation using PATH search (e.g., iterate process.env.PATH split and check fs.constants.X_OK), or document why shell is required here.
PRA-10 Resolve/justify architecture src/lib/onboard/model-router-command.ts:92 Distinguish ENOENT (return null/skip) from other errors (re-throw or log with context). Document why this cannot be fixed at the source (e.g., race condition with concurrent deletion).
PRA-11 Resolve/justify architecture src/lib/onboard/model-router-command.ts:113 Log a warning when symlink read fails, or include a marker in the fingerprint indicating unreadable links were skipped. Document why this cannot be fixed at the source.
PRA-12 Resolve/justify architecture src/lib/onboard/model-router-command.ts:134 Distinguish ENOENT (return null) from other errors (re-throw or log). Current behavior is acceptable for production but could be improved.
PRA-13 Resolve/justify architecture src/lib/onboard/model-router-command.ts:188 Remove ignoreError: true and handle failures explicitly: throw on error, or at minimum log a clear warning that submodule init failed and router source may be incomplete.
PRA-14 Improvement correctness src/lib/onboard/model-router.ts:211 Inline the function at the single call site in createStartModelRouterDeps, or promote to a module-level constant if a second usage appears.
PRA-15 Improvement correctness test/support/model-router-process-test-helpers.ts:1 Simplify createProductionModelRouterInstallFixture by using prepareModelRouterVenv directly or spawning real Node processes instead of generating shell scripts. The fixture should stay in sync with production provisioning logic.

🚨 Required before merge

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

PRA-5 Required — hashModelRouterSourceTree lacks routerDir containment validation

  • Location: src/lib/onboard/model-router-command.ts:85
  • Category: security
  • Problem: hashModelRouterSourceTree(routerDir) uses path.relative(routerDir, entryPath) without validating routerDir is absolute and within expected boundaries. The DI extraction makes this function reusable with caller-controlled inputs. If createModelRouterCommandProvisioner is called with a crafted routerDir (e.g., symlink to /etc), source fingerprinting could read arbitrary files outside the model router source tree.
  • Impact: Path traversal in source fingerprinting allows reading arbitrary files if routerDir is attacker-controlled. The exported createModelRouterCommandProvisioner and isManagedModelRouterCurrent accept caller-controlled routerDir parameters.
  • Required action: Add validation at function entry: assert path.isAbsolute(routerDir) and resolve to realpath via fs.realpathSync before walking. Throw if resolved path escapes expected root (e.g., not under paths.rootDir).
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read hashModelRouterSourceTree function (lines 85-130) and trace routerDir origin from createModelRouterCommandProvisioner caller at model-router.ts:152 and exported isManagedModelRouterCurrent at model-router.ts:174.
  • Missing regression test: Add test: createCommandHarness with routerDir as symlink to /etc/passwd, verify hashModelRouterSourceTree does not read outside routerDir or throws an error.
  • Done when: The required change is committed and verification passes: Read hashModelRouterSourceTree function (lines 85-130) and trace routerDir origin from createModelRouterCommandProvisioner caller at model-router.ts:152 and exported isManagedModelRouterCurrent at model-router.ts:174.
  • Evidence: Function hashModelRouterSourceTree at model-router-command.ts:85-130 does not validate routerDir before path.relative call. Previous review PRA-5 identical.

PRA-6 Required — Symlink targets hashed without containment check in source fingerprinting

  • Location: src/lib/onboard/model-router-command.ts:110
  • Category: security
  • Problem: When encountering a symbolic link, hashModelRouterSourceTree reads the link target via fs.readlinkSync(entryPath) and includes it in the fingerprint without validating the target resolves within routerDir. A malicious symlink pointing to /etc/passwd would include that external path in the fingerprint.
  • Impact: If model router source tree contains a malicious symlink (e.g., from compromised submodule or local FS attack), the fingerprint would leak external paths. Could also cause fingerprint instability if symlink targets change.
  • Required action: Validate symlink targets resolve within routerDir: use fs.realpathSync on entryPath and check it starts with realpath(routerDir). Skip or error on external symlinks.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read lines 110-118 in model-router-command.ts where isSymbolicLink() is handled.
  • Missing regression test: Add test: create symlink in routerDir pointing to /tmp/outside, verify fingerprint does not include external path or function throws.
  • Done when: The required change is committed and verification passes: Read lines 110-118 in model-router-command.ts where isSymbolicLink() is handled.
  • Evidence: Symlink handling at model-router-command.ts:110-118 hashes link target without containment validation. Previous review PRA-6 identical.

PRA-7 Required — Git submodule initialization trust assumption undocumented and errors silenced

  • Location: src/lib/onboard/model-router-command.ts:185
  • Category: security
  • Problem: initializeModelRouterSubmodule runs git submodule update --init --depth 1 with ignoreError: true. If .gitmodules contains a malicious URL, this fetches untrusted code. The --depth 1 limits history but not the initial clone. ignoreError: true masks clone failures that should be visible.
  • Impact: Supply chain risk if .gitmodules is compromised (e.g., via PR that modifies submodule URL). Silent failures hide operational problems.
  • Required action: Add code comment documenting that .gitmodules is trusted source-controlled configuration and submodule URLs must be reviewed in PRs. Consider removing ignoreError: true to surface clone failures, or at minimum log a warning on failure.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read initializeModelRouterSubmodule function (lines 178-192) and confirm .gitmodules is in repo root.
  • Missing regression test: Add test: verify submodule init failure is logged/propagated, not silently ignored.
  • Done when: The required change is committed and verification passes: Read initializeModelRouterSubmodule function (lines 178-192) and confirm .gitmodules is in repo root.
  • Evidence: Submodule init at model-router-command.ts:185-192 uses ignoreError: true without trust documentation. Previous review PRA-7 identical.

PRA-8 Required — Missing negative security tests for enabled attack vectors

  • Location: test/onboard-model-router.test.ts:1
  • Category: tests
  • Problem: The rewritten test suite covers functional behavior but lacks security-focused test cases for the attack vectors enabled by PRA-5, PRA-6, PRA-7, and PRA-12.
  • Impact: No automated verification that path traversal, symlink escape, submodule URL injection, or shell injection are prevented. Regression risk for security fixes.
  • Required action: Add security-focused test cases to createCommandHarness tests: (1) routerDir as symlink to sensitive area, (2) symlink in source tree pointing outside, (3) verify pip install command uses argv array not shell string, (4) verify error messages don't leak credentials, (5) verify submodule init failure handling.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Search test/onboard-model-router.test.ts for security-focused test cases covering path traversal, symlink containment, submodule trust, shell injection.
  • Missing regression test: Five specific negative security test cases as described in recommendation.
  • Done when: The required change is committed and verification passes: Search test/onboard-model-router.test.ts for security-focused test cases covering path traversal, symlink containment, submodule trust, shell injection.
  • Evidence: New test file (458 lines) has 8 functional tests but zero negative security tests.
Review findings by urgency: 4 required fixes, 9 items to resolve/justify, 2 in-scope improvements

⚠️ Resolve or justify before merge

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

PRA-1 Resolve/justify — Source-of-truth review needed: src/lib/onboard/model-router-command.ts:92 (hashDirectory readdirSync catch)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Simulate EACCES/ENOENT on readdirSync, verify graceful false return without masking real errors
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: Silent catch at model-router-command.ts:92-100 returns false for all errors

PRA-2 Resolve/justify — Source-of-truth review needed: src/lib/onboard/model-router-command.ts:113 (symlink readlinkSync catch)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Create unreadable symlink in routerDir, verify warning logged or fingerprint includes marker for skipped links
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: Silent catch at model-router-command.ts:113 skips unreadable links without visibility

PRA-3 Resolve/justify — Source-of-truth review needed: src/lib/onboard/model-router-command.ts:134 (readModelRouterInstalledFingerprint catch-all)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Simulate permission error on fingerprint read, verify error is not silently swallowed (throw or log)
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: Catch-all at model-router-command.ts:134-142 returns null for all errors

PRA-4 Resolve/justify — Source-of-truth review needed: src/lib/onboard/model-router-command.ts:188 (git submodule update ignoreError: true)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Simulate git submodule update failure, verify error is surfaced (throw or warning log)
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: ignoreError: true at model-router-command.ts:188 silently ignores all submodule init failures

PRA-9 Resolve/justify — Shell string execution in resolveHostCommandPath

  • Location: src/lib/onboard/model-router-command.ts:245
  • Category: security
  • Problem: resolveHostCommandPath uses deps.runCapture(["sh", "-c", 'command -v "$1"', "--", "model-router"]) which executes a shell string. While the command is hardcoded, this pattern is fragile and could be exploited if the command name becomes dynamic.
  • Impact: Shell injection risk if command name becomes user-controlled in future. Violates principle of avoiding shell when argv array works.
  • Recommended action: Replace with a non-shell implementation using PATH search (e.g., iterate process.env.PATH split and check fs.constants.X_OK), or document why shell is required here.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read resolveHostCommandPath function at line 245.
  • Missing regression test: Add test: verify resolveHostCommandPath finds command via PATH search without shell.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read resolveHostCommandPath function at line 245.
  • Evidence: Shell string at model-router-command.ts:245. Previous review PRA-12 identical.

PRA-10 Resolve/justify — Source-of-truth review: hashModelRouterSourceTree/hashDirectory silent catch on readdirSync

  • Location: src/lib/onboard/model-router-command.ts:92
  • Category: architecture
  • Problem: hashDirectory catches all errors from readdirSync and returns false, silently skipping directories that can't be read. This masks permission errors, missing directories, or filesystem issues that should be visible.
  • Impact: Source fingerprinting may produce incomplete/incorrect fingerprints without warning. Could cause unnecessary reinstalls or miss source changes.
  • Recommended action: Distinguish ENOENT (return null/skip) from other errors (re-throw or log with context). Document why this cannot be fixed at the source (e.g., race condition with concurrent deletion).
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read hashDirectory function lines 92-100.
  • Missing regression test: Add test: simulate readdirSync permission error, verify appropriate error handling (log/throw vs silent skip).
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read hashDirectory function lines 92-100.
  • Evidence: Silent catch at model-router-command.ts:92-100. Previous review PRA-1 identical.

PRA-11 Resolve/justify — Source-of-truth review: hashModelRouterSourceTree silent catch on symlink read

  • Location: src/lib/onboard/model-router-command.ts:113
  • Category: architecture
  • Problem: Symlink handling catches all errors from fs.readlinkSync and silently continues, skipping unreadable links. Comment says 'the install step will fail if they are required' but this is a workaround, not a fix.
  • Impact: Unreadable symlinks are excluded from fingerprint without visibility. Could cause fingerprint mismatch if symlink becomes readable later, or hide filesystem issues.
  • Recommended action: Log a warning when symlink read fails, or include a marker in the fingerprint indicating unreadable links were skipped. Document why this cannot be fixed at the source.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read lines 110-118 where isSymbolicLink() catch block is.
  • Missing regression test: Add test: create unreadable symlink, verify warning logged or fingerprint includes marker.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read lines 110-118 where isSymbolicLink() catch block is.
  • Evidence: Silent catch at model-router-command.ts:113. Previous review PRA-2 identical.

PRA-12 Resolve/justify — Source-of-truth review: readModelRouterInstalledFingerprint silent catch-all

  • Location: src/lib/onboard/model-router-command.ts:134
  • Category: architecture
  • Problem: readModelRouterInstalledFingerprint catches all errors and returns null, indistinguishable from file-not-found vs permission error vs corruption.
  • Impact: Permission errors or corruption treated same as missing fingerprint, potentially triggering unnecessary reinstalls or masking real issues.
  • Recommended action: Distinguish ENOENT (return null) from other errors (re-throw or log). Current behavior is acceptable for production but could be improved.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read readModelRouterInstalledFingerprint function lines 134-142.
  • Missing regression test: Add test: simulate permission error on fingerprint read, verify error is not silently swallowed.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read readModelRouterInstalledFingerprint function lines 134-142.
  • Evidence: Silent catch-all at model-router-command.ts:134-142. Previous review PRA-3 identical.

PRA-13 Resolve/justify — Source-of-truth review: initializeModelRouterSubmodule git submodule update with ignoreError: true

  • Location: src/lib/onboard/model-router-command.ts:188
  • Category: architecture
  • Problem: initializeModelRouterSubmodule runs git submodule update with ignoreError: true, silently ignoring any failure including network issues, URL changes, or auth failures.
  • Impact: Submodule initialization failures are invisible. Router source may be incomplete without any indication.
  • Recommended action: Remove ignoreError: true and handle failures explicitly: throw on error, or at minimum log a clear warning that submodule init failed and router source may be incomplete.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read initializeModelRouterSubmodule function lines 178-192.
  • Missing regression test: Add test: simulate git submodule update failure, verify error is surfaced (thrown or logged).
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read initializeModelRouterSubmodule function lines 178-192.
  • Evidence: ignoreError: true at model-router-command.ts:188. Previous review PRA-4 identical.

💡 In-scope improvements

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

PRA-14 Improvement — getProviderKey DI default duplicated from old inline usage

  • Location: src/lib/onboard/model-router.ts:211
  • Category: correctness
  • Problem: getProviderKey: () => (process.env.NEMOCLAW_PROVIDER_KEY || '').trim() is duplicated from the old inline implementation. Only used once in createStartModelRouterDeps; could be inlined or promoted to constant.
  • Impact: Minor code duplication. Current DI pattern is correct and testable.
  • Suggested action: Inline the function at the single call site in createStartModelRouterDeps, or promote to a module-level constant if a second usage appears.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check if getProviderKey is used elsewhere in the codebase; it is only referenced in createStartModelRouterDeps.
  • Missing regression test: N/A - not a bug
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Single-use DI default at model-router.ts:211.

PRA-15 Improvement — New test helper file adds infrastructure that could be simplified

  • Location: test/support/model-router-process-test-helpers.ts:1
  • Category: correctness
  • Problem: model-router-process-test-helpers.ts (171 lines) adds a fixture factory with shell script generation for python version detection, venv creation, pip install simulation. This duplicates production provisioning logic in tests.
  • Impact: Increased test maintenance burden. Shell script generation in tests is fragile and duplicates production logic. Could be simplified by reusing existing test helpers or using direct Node.js process spawning.
  • Suggested action: Simplify createProductionModelRouterInstallFixture by using prepareModelRouterVenv directly or spawning real Node processes instead of generating shell scripts. The fixture should stay in sync with production provisioning logic.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Read test/support/model-router-process-test-helpers.ts and compare with production provisioning logic in model-router-command.ts.
  • Missing regression test: N/A - architecture suggestion
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: New 171-line test helper with shell script generation for Python/venv/pip simulation.
Simplification opportunities: 2 possible cuts, net -83 lines possible

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

  • PRA-14 shrink (src/lib/onboard/model-router.ts:211): getProviderKey: () => (process.env.NEMOCLAW_PROVIDER_KEY || '').trim(), in StartModelRouterDeps and createStartModelRouterDeps
    • Replacement: Inline as () => (process.env.NEMOCLAW_PROVIDER_KEY || '').trim() at the call site in createStartModelRouterDeps
    • Net: -3 lines
    • Safety boundary: No behavior change; pure inline of single-use function
  • PRA-15 stdlib (test/support/model-router-process-test-helpers.ts:1): Shell script generation for python3, venv creation, pip install simulation in createProductionModelRouterInstallFixture (approx 120 lines)
    • Replacement: Use prepareModelRouterVenv directly in tests or spawn real Node.js child processes with simple argv arrays
    • Net: -80 lines
    • Safety boundary: Test-only code; production provisioning logic unchanged
Test follow-ups to resolve or justify

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

  • PRA-T1 Runtime validation — routerDir as symlink to /etc/passwd - verify path traversal blocked. Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/onboard/model-router-command.ts, src/lib/onboard/model-router.ts. Retained lightweight real process contract covers config generation, detached launch, credential env filtering, PID liveness, cleanup - adequate for integration contract. Missing negative security tests for attack vectors.
  • PRA-T2 Runtime validation — Symlink in source tree pointing outside routerDir - verify symlink escape blocked. Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/onboard/model-router-command.ts, src/lib/onboard/model-router.ts. Retained lightweight real process contract covers config generation, detached launch, credential env filtering, PID liveness, cleanup - adequate for integration contract. Missing negative security tests for attack vectors.
  • PRA-T3 Runtime validation — resolveHostCommandPath shell injection - verify argv array used not shell string. Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/onboard/model-router-command.ts, src/lib/onboard/model-router.ts. Retained lightweight real process contract covers config generation, detached launch, credential env filtering, PID liveness, cleanup - adequate for integration contract. Missing negative security tests for attack vectors.
  • PRA-T4 Runtime validation — Credential leakage in error messages - verify secrets not in stderr/throw. Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/onboard/model-router-command.ts, src/lib/onboard/model-router.ts. Retained lightweight real process contract covers config generation, detached launch, credential env filtering, PID liveness, cleanup - adequate for integration contract. Missing negative security tests for attack vectors.
  • PRA-T5 Runtime validation — Submodule init failure handling - verify error surfaced not silently ignored. Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/onboard/model-router-command.ts, src/lib/onboard/model-router.ts. Retained lightweight real process contract covers config generation, detached launch, credential env filtering, PID liveness, cleanup - adequate for integration contract. Missing negative security tests for attack vectors.
  • PRA-T6 Missing negative security tests for enabled attack vectors — Add security-focused test cases to createCommandHarness tests: (1) routerDir as symlink to sensitive area, (2) symlink in source tree pointing outside, (3) verify pip install command uses argv array not shell string, (4) verify error messages don't leak credentials, (5) verify submodule init failure handling.
  • PRA-T7 Acceptance clause — Reduce test execution 11.997s to 0.251s — add test evidence or identify existing coverage. Claimed in PR description; not independently verified in this review
  • PRA-T8 Acceptance clause — Security hardening for path traversal, symlink escape, submodule trust, shell injection — add test evidence or identify existing coverage. All 4 blocker findings from previous review (PRA-5, PRA-6, PRA-7, PRA-12) persist identically in new model-router-command.ts file
Since last review details

Current findings, using the urgency labels above:

PRA-1 Resolve/justify — Source-of-truth review needed: src/lib/onboard/model-router-command.ts:92 (hashDirectory readdirSync catch)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Simulate EACCES/ENOENT on readdirSync, verify graceful false return without masking real errors
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: Silent catch at model-router-command.ts:92-100 returns false for all errors

PRA-2 Resolve/justify — Source-of-truth review needed: src/lib/onboard/model-router-command.ts:113 (symlink readlinkSync catch)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Create unreadable symlink in routerDir, verify warning logged or fingerprint includes marker for skipped links
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: Silent catch at model-router-command.ts:113 skips unreadable links without visibility

PRA-3 Resolve/justify — Source-of-truth review needed: src/lib/onboard/model-router-command.ts:134 (readModelRouterInstalledFingerprint catch-all)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Simulate permission error on fingerprint read, verify error is not silently swallowed (throw or log)
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: Catch-all at model-router-command.ts:134-142 returns null for all errors

PRA-4 Resolve/justify — Source-of-truth review needed: src/lib/onboard/model-router-command.ts:188 (git submodule update ignoreError: true)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Simulate git submodule update failure, verify error is surfaced (throw or warning log)
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: ignoreError: true at model-router-command.ts:188 silently ignores all submodule init failures

PRA-5 Required — hashModelRouterSourceTree lacks routerDir containment validation

  • Location: src/lib/onboard/model-router-command.ts:85
  • Category: security
  • Problem: hashModelRouterSourceTree(routerDir) uses path.relative(routerDir, entryPath) without validating routerDir is absolute and within expected boundaries. The DI extraction makes this function reusable with caller-controlled inputs. If createModelRouterCommandProvisioner is called with a crafted routerDir (e.g., symlink to /etc), source fingerprinting could read arbitrary files outside the model router source tree.
  • Impact: Path traversal in source fingerprinting allows reading arbitrary files if routerDir is attacker-controlled. The exported createModelRouterCommandProvisioner and isManagedModelRouterCurrent accept caller-controlled routerDir parameters.
  • Required action: Add validation at function entry: assert path.isAbsolute(routerDir) and resolve to realpath via fs.realpathSync before walking. Throw if resolved path escapes expected root (e.g., not under paths.rootDir).
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read hashModelRouterSourceTree function (lines 85-130) and trace routerDir origin from createModelRouterCommandProvisioner caller at model-router.ts:152 and exported isManagedModelRouterCurrent at model-router.ts:174.
  • Missing regression test: Add test: createCommandHarness with routerDir as symlink to /etc/passwd, verify hashModelRouterSourceTree does not read outside routerDir or throws an error.
  • Done when: The required change is committed and verification passes: Read hashModelRouterSourceTree function (lines 85-130) and trace routerDir origin from createModelRouterCommandProvisioner caller at model-router.ts:152 and exported isManagedModelRouterCurrent at model-router.ts:174.
  • Evidence: Function hashModelRouterSourceTree at model-router-command.ts:85-130 does not validate routerDir before path.relative call. Previous review PRA-5 identical.

PRA-6 Required — Symlink targets hashed without containment check in source fingerprinting

  • Location: src/lib/onboard/model-router-command.ts:110
  • Category: security
  • Problem: When encountering a symbolic link, hashModelRouterSourceTree reads the link target via fs.readlinkSync(entryPath) and includes it in the fingerprint without validating the target resolves within routerDir. A malicious symlink pointing to /etc/passwd would include that external path in the fingerprint.
  • Impact: If model router source tree contains a malicious symlink (e.g., from compromised submodule or local FS attack), the fingerprint would leak external paths. Could also cause fingerprint instability if symlink targets change.
  • Required action: Validate symlink targets resolve within routerDir: use fs.realpathSync on entryPath and check it starts with realpath(routerDir). Skip or error on external symlinks.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read lines 110-118 in model-router-command.ts where isSymbolicLink() is handled.
  • Missing regression test: Add test: create symlink in routerDir pointing to /tmp/outside, verify fingerprint does not include external path or function throws.
  • Done when: The required change is committed and verification passes: Read lines 110-118 in model-router-command.ts where isSymbolicLink() is handled.
  • Evidence: Symlink handling at model-router-command.ts:110-118 hashes link target without containment validation. Previous review PRA-6 identical.

PRA-7 Required — Git submodule initialization trust assumption undocumented and errors silenced

  • Location: src/lib/onboard/model-router-command.ts:185
  • Category: security
  • Problem: initializeModelRouterSubmodule runs git submodule update --init --depth 1 with ignoreError: true. If .gitmodules contains a malicious URL, this fetches untrusted code. The --depth 1 limits history but not the initial clone. ignoreError: true masks clone failures that should be visible.
  • Impact: Supply chain risk if .gitmodules is compromised (e.g., via PR that modifies submodule URL). Silent failures hide operational problems.
  • Required action: Add code comment documenting that .gitmodules is trusted source-controlled configuration and submodule URLs must be reviewed in PRs. Consider removing ignoreError: true to surface clone failures, or at minimum log a warning on failure.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read initializeModelRouterSubmodule function (lines 178-192) and confirm .gitmodules is in repo root.
  • Missing regression test: Add test: verify submodule init failure is logged/propagated, not silently ignored.
  • Done when: The required change is committed and verification passes: Read initializeModelRouterSubmodule function (lines 178-192) and confirm .gitmodules is in repo root.
  • Evidence: Submodule init at model-router-command.ts:185-192 uses ignoreError: true without trust documentation. Previous review PRA-7 identical.

PRA-8 Required — Missing negative security tests for enabled attack vectors

  • Location: test/onboard-model-router.test.ts:1
  • Category: tests
  • Problem: The rewritten test suite covers functional behavior but lacks security-focused test cases for the attack vectors enabled by PRA-5, PRA-6, PRA-7, and PRA-12.
  • Impact: No automated verification that path traversal, symlink escape, submodule URL injection, or shell injection are prevented. Regression risk for security fixes.
  • Required action: Add security-focused test cases to createCommandHarness tests: (1) routerDir as symlink to sensitive area, (2) symlink in source tree pointing outside, (3) verify pip install command uses argv array not shell string, (4) verify error messages don't leak credentials, (5) verify submodule init failure handling.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Search test/onboard-model-router.test.ts for security-focused test cases covering path traversal, symlink containment, submodule trust, shell injection.
  • Missing regression test: Five specific negative security test cases as described in recommendation.
  • Done when: The required change is committed and verification passes: Search test/onboard-model-router.test.ts for security-focused test cases covering path traversal, symlink containment, submodule trust, shell injection.
  • Evidence: New test file (458 lines) has 8 functional tests but zero negative security tests.

PRA-9 Resolve/justify — Shell string execution in resolveHostCommandPath

  • Location: src/lib/onboard/model-router-command.ts:245
  • Category: security
  • Problem: resolveHostCommandPath uses deps.runCapture(["sh", "-c", 'command -v "$1"', "--", "model-router"]) which executes a shell string. While the command is hardcoded, this pattern is fragile and could be exploited if the command name becomes dynamic.
  • Impact: Shell injection risk if command name becomes user-controlled in future. Violates principle of avoiding shell when argv array works.
  • Recommended action: Replace with a non-shell implementation using PATH search (e.g., iterate process.env.PATH split and check fs.constants.X_OK), or document why shell is required here.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read resolveHostCommandPath function at line 245.
  • Missing regression test: Add test: verify resolveHostCommandPath finds command via PATH search without shell.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read resolveHostCommandPath function at line 245.
  • Evidence: Shell string at model-router-command.ts:245. Previous review PRA-12 identical.

PRA-10 Resolve/justify — Source-of-truth review: hashModelRouterSourceTree/hashDirectory silent catch on readdirSync

  • Location: src/lib/onboard/model-router-command.ts:92
  • Category: architecture
  • Problem: hashDirectory catches all errors from readdirSync and returns false, silently skipping directories that can't be read. This masks permission errors, missing directories, or filesystem issues that should be visible.
  • Impact: Source fingerprinting may produce incomplete/incorrect fingerprints without warning. Could cause unnecessary reinstalls or miss source changes.
  • Recommended action: Distinguish ENOENT (return null/skip) from other errors (re-throw or log with context). Document why this cannot be fixed at the source (e.g., race condition with concurrent deletion).
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read hashDirectory function lines 92-100.
  • Missing regression test: Add test: simulate readdirSync permission error, verify appropriate error handling (log/throw vs silent skip).
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read hashDirectory function lines 92-100.
  • Evidence: Silent catch at model-router-command.ts:92-100. Previous review PRA-1 identical.

PRA-11 Resolve/justify — Source-of-truth review: hashModelRouterSourceTree silent catch on symlink read

  • Location: src/lib/onboard/model-router-command.ts:113
  • Category: architecture
  • Problem: Symlink handling catches all errors from fs.readlinkSync and silently continues, skipping unreadable links. Comment says 'the install step will fail if they are required' but this is a workaround, not a fix.
  • Impact: Unreadable symlinks are excluded from fingerprint without visibility. Could cause fingerprint mismatch if symlink becomes readable later, or hide filesystem issues.
  • Recommended action: Log a warning when symlink read fails, or include a marker in the fingerprint indicating unreadable links were skipped. Document why this cannot be fixed at the source.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read lines 110-118 where isSymbolicLink() catch block is.
  • Missing regression test: Add test: create unreadable symlink, verify warning logged or fingerprint includes marker.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read lines 110-118 where isSymbolicLink() catch block is.
  • Evidence: Silent catch at model-router-command.ts:113. Previous review PRA-2 identical.

PRA-12 Resolve/justify — Source-of-truth review: readModelRouterInstalledFingerprint silent catch-all

  • Location: src/lib/onboard/model-router-command.ts:134
  • Category: architecture
  • Problem: readModelRouterInstalledFingerprint catches all errors and returns null, indistinguishable from file-not-found vs permission error vs corruption.
  • Impact: Permission errors or corruption treated same as missing fingerprint, potentially triggering unnecessary reinstalls or masking real issues.
  • Recommended action: Distinguish ENOENT (return null) from other errors (re-throw or log). Current behavior is acceptable for production but could be improved.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read readModelRouterInstalledFingerprint function lines 134-142.
  • Missing regression test: Add test: simulate permission error on fingerprint read, verify error is not silently swallowed.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read readModelRouterInstalledFingerprint function lines 134-142.
  • Evidence: Silent catch-all at model-router-command.ts:134-142. Previous review PRA-3 identical.

PRA-13 Resolve/justify — Source-of-truth review: initializeModelRouterSubmodule git submodule update with ignoreError: true

  • Location: src/lib/onboard/model-router-command.ts:188
  • Category: architecture
  • Problem: initializeModelRouterSubmodule runs git submodule update with ignoreError: true, silently ignoring any failure including network issues, URL changes, or auth failures.
  • Impact: Submodule initialization failures are invisible. Router source may be incomplete without any indication.
  • Recommended action: Remove ignoreError: true and handle failures explicitly: throw on error, or at minimum log a clear warning that submodule init failed and router source may be incomplete.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read initializeModelRouterSubmodule function lines 178-192.
  • Missing regression test: Add test: simulate git submodule update failure, verify error is surfaced (thrown or logged).
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read initializeModelRouterSubmodule function lines 178-192.
  • Evidence: ignoreError: true at model-router-command.ts:188. Previous review PRA-4 identical.

PRA-14 Improvement — getProviderKey DI default duplicated from old inline usage

  • Location: src/lib/onboard/model-router.ts:211
  • Category: correctness
  • Problem: getProviderKey: () => (process.env.NEMOCLAW_PROVIDER_KEY || '').trim() is duplicated from the old inline implementation. Only used once in createStartModelRouterDeps; could be inlined or promoted to constant.
  • Impact: Minor code duplication. Current DI pattern is correct and testable.
  • Suggested action: Inline the function at the single call site in createStartModelRouterDeps, or promote to a module-level constant if a second usage appears.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check if getProviderKey is used elsewhere in the codebase; it is only referenced in createStartModelRouterDeps.
  • Missing regression test: N/A - not a bug
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Single-use DI default at model-router.ts:211.

PRA-15 Improvement — New test helper file adds infrastructure that could be simplified

  • Location: test/support/model-router-process-test-helpers.ts:1
  • Category: correctness
  • Problem: model-router-process-test-helpers.ts (171 lines) adds a fixture factory with shell script generation for python version detection, venv creation, pip install simulation. This duplicates production provisioning logic in tests.
  • Impact: Increased test maintenance burden. Shell script generation in tests is fragile and duplicates production logic. Could be simplified by reusing existing test helpers or using direct Node.js process spawning.
  • Suggested action: Simplify createProductionModelRouterInstallFixture by using prepareModelRouterVenv directly or spawning real Node processes instead of generating shell scripts. The fixture should stay in sync with production provisioning logic.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Read test/support/model-router-process-test-helpers.ts and compare with production provisioning logic in model-router-command.ts.
  • Missing regression test: N/A - architecture suggestion
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: New 171-line test helper with shell script generation for Python/venv/pip simulation.

Workflow run details

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

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Changes requested

Merge posture: Do not merge yet
Primary next action: Resolve or justify PRA-1: Source-of-truth review needed: Model Router fallback install fingerprint.
Open items: 0 required · 2 warnings · 0 suggestions · 6 test follow-ups
Since last review: 0 prior items resolved · 2 still apply · 0 new items found

Action checklist

  • PRA-1 Resolve or justify: Source-of-truth review needed: Model Router fallback install fingerprint
  • PRA-2 Resolve or justify: Prove the production null-fingerprint fallback instead of injecting it in test/onboard-model-router.test.ts:449
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Runtime validation
  • PRA-T3 Add or justify test follow-up: Acceptance clause
  • PRA-T4 Add or justify test follow-up: Acceptance clause
  • PRA-T5 Add or justify test follow-up: Acceptance clause
  • PRA-T6 Add or justify test follow-up: Model Router fallback install fingerprint

Findings index

ID Severity Category Location Required action
PRA-1 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-2 Resolve/justify security test/onboard-model-router.test.ts:449 Add a focused production-path fixture or document the exact packaged/unreadable-source state this fallback is intended to support. The test should use the real production fingerprint discovery, make git and source-tree hashing unavailable in the same way production can encounter, assert `install:<packageVersion>` is written and accepted only in that state, and assert `install:*` is rejected when a source-tree `files:<sha>` fingerprint is available.
Review findings by urgency: 0 required fixes, 2 items to resolve/justify, 0 in-scope improvements

⚠️ Resolve or justify before merge

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

PRA-1 Resolve/justify — Source-of-truth review needed: Model Router fallback install fingerprint

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Missing production-path regression; current coverage injects `sourceFingerprint: () => null` and therefore bypasses the production source boundary.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: `installModelRouterCommand()` writes `sourceFingerprint ?? install:<packageVersion>`, and `isManagedModelRouterCurrent()` accepts an existing `install:*` marker when `getSourceFingerprint()` returns null; the test at `test/onboard-model-router.test.ts:449` forces null via dependency injection.

PRA-2 Resolve/justify — Prove the production null-fingerprint fallback instead of injecting it

  • Location: test/onboard-model-router.test.ts:449
  • Category: security
  • Problem: The fallback fingerprint test still constructs the provisioner with `sourceFingerprint: () => null`, so it validates behavior only after a dependency reports no fingerprint. Production code first tries `git -C <routerDir> rev-parse HEAD`, then `git -C <rootDir> rev-parse HEAD:<relativeRouterDir>`, then `hashModelRouterSourceTree(paths.routerDir)`. Because installation requires `pyproject.toml` or `setup.py`, an ordinary non-git router source should still produce a `files:<sha>` fingerprint, leaving the real production state that permits `install:<packageVersion>` reuse unclear.
  • Impact: The managed installer trust boundary can continue accepting an `install:*` marker without a regression proving when production may enter that tolerant path. A future fingerprinting change could make the fallback too broad, stale-install-tolerant, or unreachable while this test still passes.
  • Recommended action: Add a focused production-path fixture or document the exact packaged/unreadable-source state this fallback is intended to support. The test should use the real production fingerprint discovery, make git and source-tree hashing unavailable in the same way production can encounter, assert `install:<packageVersion>` is written and accepted only in that state, and assert `install:*` is rejected when a source-tree `files:<sha>` fingerprint is available.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read `src/lib/onboard/model-router-command.ts` around `getSourceFingerprint()` and compare it with `test/onboard-model-router.test.ts` around `writes fallback fingerprint file when git source fingerprint is unavailable`; confirm the test bypasses git HEAD, gitlink, and `hashModelRouterSourceTree()` by injecting `sourceFingerprint: () => null`.
  • Missing regression test: Add `production fallback install fingerprint is accepted only when git and source-tree fingerprinting are unavailable`: instantiate the production provisioner without `sourceFingerprint`, force git probes to fail, make the router source unavailable or unreadable in the real packaging mode that yields no source-tree hash, install the managed command, assert `.nemoclaw-source-fingerprint` is `install:<packageVersion>`, then assert an existing `install:*` marker is not accepted when the same router directory can produce a `files:<sha>` fingerprint.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read `src/lib/onboard/model-router-command.ts` around `getSourceFingerprint()` and compare it with `test/onboard-model-router.test.ts` around `writes fallback fingerprint file when git source fingerprint is unavailable`; confirm the test bypasses git HEAD, gitlink, and `hashModelRouterSourceTree()` by injecting `sourceFingerprint: () => null`.
  • Evidence: `test/onboard-model-router.test.ts` uses `createCommandHarness({ sourceFingerprint: () => null })`; `src/lib/onboard/model-router-command.ts` otherwise falls back from git probes to `hashModelRouterSourceTree(paths.routerDir)` before writing or accepting `install:<version>`.

💡 In-scope improvements

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

  • None.
Test follow-ups to resolve or justify

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

  • PRA-T1 Runtime validation — Add `production fallback install fingerprint is written only when git and source-tree fingerprinting are unavailable` using the production provisioner without `sourceFingerprint: () => null`.. The changed production surfaces include installer and process-launch boundaries. Existing tests provide targeted runtime-style coverage for managed venv provisioning, PATH avoidance, process launch args/env, PID liveness, and cleanup; the remaining runtime confidence gap is the production source-fingerprint fallback path.
  • PRA-T2 Runtime validation — Add `install-prefixed fingerprint is rejected when router source produces a files fingerprint` to prove `install:*` does not mask a hashable source-tree change.. The changed production surfaces include installer and process-launch boundaries. Existing tests provide targeted runtime-style coverage for managed venv provisioning, PATH avoidance, process launch args/env, PID liveness, and cleanup; the remaining runtime confidence gap is the production source-fingerprint fallback path.
  • PRA-T3 Acceptance clause — Refs perf(test): reduce test I/O to restore 2–5 minute full-suite runs #6245 — add test evidence or identify existing coverage. The deterministic validation context did not include linked issue perf(test): reduce test I/O to restore 2–5 minute full-suite runs #6245 body or comments, so no literal issue clauses could be extracted or mapped.
  • PRA-T4 Acceptance clause — Replace five source-loader child drivers with seven direct tests covering routed-provider wiring, managed command installation/reuse/refresh, fallback fingerprints, production adapter composition, and real proxy launch arguments. — add test evidence or identify existing coverage. `test/onboard-model-router.test.ts` has direct tests for routed-provider wiring, production command currentness/install, managed install vs PATH, PATH preference, stale refresh, fallback fingerprint behavior, and production process launch arguments. The fallback fingerprint coverage is partial because it injects `sourceFingerprint: () => null` instead of exercising the production fingerprint discovery boundary.
  • PRA-T5 Acceptance clause — Tests added or updated for changed behavior — add test evidence or identify existing coverage. `test/onboard-model-router.test.ts` and `test/support/model-router-process-test-helpers.ts` were updated/added and cover the main refactor and process-launch behavior. The remaining coverage gap is the production-path fallback install fingerprint behavior.
  • PRA-T6 Model Router fallback install fingerprint — Missing production-path regression; current coverage injects `sourceFingerprint: () => null` and therefore bypasses the production source boundary.. `installModelRouterCommand()` writes `sourceFingerprint ?? install:<packageVersion>`, and `isManagedModelRouterCurrent()` accepts an existing `install:*` marker when `getSourceFingerprint()` returns null; the test at `test/onboard-model-router.test.ts:449` forces null via dependency injection.
Since last review details

Current findings, using the urgency labels above:

PRA-1 Resolve/justify — Source-of-truth review needed: Model Router fallback install fingerprint

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Missing production-path regression; current coverage injects `sourceFingerprint: () => null` and therefore bypasses the production source boundary.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: `installModelRouterCommand()` writes `sourceFingerprint ?? install:<packageVersion>`, and `isManagedModelRouterCurrent()` accepts an existing `install:*` marker when `getSourceFingerprint()` returns null; the test at `test/onboard-model-router.test.ts:449` forces null via dependency injection.

PRA-2 Resolve/justify — Prove the production null-fingerprint fallback instead of injecting it

  • Location: test/onboard-model-router.test.ts:449
  • Category: security
  • Problem: The fallback fingerprint test still constructs the provisioner with `sourceFingerprint: () => null`, so it validates behavior only after a dependency reports no fingerprint. Production code first tries `git -C <routerDir> rev-parse HEAD`, then `git -C <rootDir> rev-parse HEAD:<relativeRouterDir>`, then `hashModelRouterSourceTree(paths.routerDir)`. Because installation requires `pyproject.toml` or `setup.py`, an ordinary non-git router source should still produce a `files:<sha>` fingerprint, leaving the real production state that permits `install:<packageVersion>` reuse unclear.
  • Impact: The managed installer trust boundary can continue accepting an `install:*` marker without a regression proving when production may enter that tolerant path. A future fingerprinting change could make the fallback too broad, stale-install-tolerant, or unreachable while this test still passes.
  • Recommended action: Add a focused production-path fixture or document the exact packaged/unreadable-source state this fallback is intended to support. The test should use the real production fingerprint discovery, make git and source-tree hashing unavailable in the same way production can encounter, assert `install:<packageVersion>` is written and accepted only in that state, and assert `install:*` is rejected when a source-tree `files:<sha>` fingerprint is available.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read `src/lib/onboard/model-router-command.ts` around `getSourceFingerprint()` and compare it with `test/onboard-model-router.test.ts` around `writes fallback fingerprint file when git source fingerprint is unavailable`; confirm the test bypasses git HEAD, gitlink, and `hashModelRouterSourceTree()` by injecting `sourceFingerprint: () => null`.
  • Missing regression test: Add `production fallback install fingerprint is accepted only when git and source-tree fingerprinting are unavailable`: instantiate the production provisioner without `sourceFingerprint`, force git probes to fail, make the router source unavailable or unreadable in the real packaging mode that yields no source-tree hash, install the managed command, assert `.nemoclaw-source-fingerprint` is `install:<packageVersion>`, then assert an existing `install:*` marker is not accepted when the same router directory can produce a `files:<sha>` fingerprint.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read `src/lib/onboard/model-router-command.ts` around `getSourceFingerprint()` and compare it with `test/onboard-model-router.test.ts` around `writes fallback fingerprint file when git source fingerprint is unavailable`; confirm the test bypasses git HEAD, gitlink, and `hashModelRouterSourceTree()` by injecting `sourceFingerprint: () => null`.
  • Evidence: `test/onboard-model-router.test.ts` uses `createCommandHarness({ sourceFingerprint: () => null })`; `src/lib/onboard/model-router-command.ts` otherwise falls back from git probes to `hashModelRouterSourceTree(paths.routerDir)` before writing or accepting `install:<version>`.

Workflow run details

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

@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 (1)
test/support/model-router-process-test-helpers.ts (1)

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

Duplicated polling loop (100 attempts × 10ms) across both functions.

stopTestProcess and readRouterLaunchLog repeat the same attempt-count/interval pattern. Extracting a small shared pollUntil(predicate, { attempts, intervalMs }) helper would remove the duplication and make the timeout tunable in one place.

♻️ Suggested extraction
+async function pollUntil(
+  predicate: () => boolean,
+  { attempts = 100, intervalMs = 10 }: { attempts?: number; intervalMs?: number } = {},
+): Promise<boolean> {
+  for (let i = 0; i < attempts; i++) {
+    if (predicate()) return true;
+    await new Promise((resolve) => setTimeout(resolve, intervalMs));
+  }
+  return false;
+}

Also applies to: 40-57

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/support/model-router-process-test-helpers.ts` around lines 22 - 38, The
polling loop pattern is duplicated between stopTestProcess and
readRouterLaunchLog, so extract the shared retry logic into a small helper such
as pollUntil that accepts a predicate plus attempts and intervalMs options.
Update stopTestProcess to use this helper for the isProcessAlive check, and
reuse the same helper in readRouterLaunchLog so the timeout behavior is defined
in one place and easy to tune.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/support/model-router-process-test-helpers.ts`:
- Around line 22-38: The polling loop pattern is duplicated between
stopTestProcess and readRouterLaunchLog, so extract the shared retry logic into
a small helper such as pollUntil that accepts a predicate plus attempts and
intervalMs options. Update stopTestProcess to use this helper for the
isProcessAlive check, and reuse the same helper in readRouterLaunchLog so the
timeout behavior is defined in one place and easy to tune.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7d23b578-432a-4fba-8608-8a42e53b2360

📥 Commits

Reviewing files that changed from the base of the PR and between 7888efa and e940b4a.

📒 Files selected for processing (2)
  • test/onboard-model-router.test.ts
  • test/support/model-router-process-test-helpers.ts

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Results — ✅ All requested jobs passed

Run: 28883204039
Workflow ref: codex/perf-in-process-onboard-model-router
Requested targets: (default — all supported)
Requested jobs: model-router-provider-routed-inference
Summary: 1 passed, 0 failed, 0 cancelled, 0 skipped

Job Result
model-router-provider-routed-inference ✅ success

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Results — ✅ All requested jobs passed

Run: 28883447759
Workflow ref: codex/perf-in-process-onboard-model-router
Requested targets: (default — all supported)
Requested jobs: model-router-provider-routed-inference,cloud-onboard
Summary: 2 passed, 0 failed, 0 cancelled, 0 skipped

Job Result
cloud-onboard ✅ success
model-router-provider-routed-inference ✅ success

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

Caution

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

⚠️ Outside diff range comments (1)
test/support/model-router-process-test-helpers.ts (1)

156-173: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against partial writes when reading the launch log.

The log file is appended to by a live subprocess while this function polls it. If a read lands mid-write, the trailing line can be incomplete, and JSON.parse will throw a SyntaxError that propagates out of the retry loop instead of triggering a retry — causing intermittent test failures rather than a clean timeout.

🔧 Proposed fix: tolerate partial trailing lines and retry
 export async function readRouterLaunchLog(
   logPath: string,
   expectedEntries: number,
 ): Promise<RouterLaunchLog[]> {
   for (let attempt = 0; attempt < 100; attempt++) {
     if (fs.existsSync(logPath)) {
-      const entries = fs
-        .readFileSync(logPath, "utf8")
-        .trim()
-        .split("\n")
-        .filter(Boolean)
-        .map((line) => JSON.parse(line) as RouterLaunchLog);
-      if (entries.length >= expectedEntries) return entries;
+      try {
+        const entries = fs
+          .readFileSync(logPath, "utf8")
+          .trim()
+          .split("\n")
+          .filter(Boolean)
+          .map((line) => JSON.parse(line) as RouterLaunchLog);
+        if (entries.length >= expectedEntries) return entries;
+      } catch {
+        // Partial write in progress; retry on the next attempt.
+      }
     }
     await new Promise((resolve) => setTimeout(resolve, 10));
   }
   throw new Error(`Timed out waiting for ${expectedEntries} Model Router launch log entries`);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/support/model-router-process-test-helpers.ts` around lines 156 - 173,
The readRouterLaunchLog helper currently parses every line immediately, so a
partial trailing write from the live subprocess can throw and escape the retry
loop. Update readRouterLaunchLog to tolerate incomplete JSON while polling by
catching parse failures (or skipping the last non-empty line when it looks
incomplete) and retrying until enough valid RouterLaunchLog entries are
available, instead of letting a SyntaxError abort the function.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@test/support/model-router-process-test-helpers.ts`:
- Around line 156-173: The readRouterLaunchLog helper currently parses every
line immediately, so a partial trailing write from the live subprocess can throw
and escape the retry loop. Update readRouterLaunchLog to tolerate incomplete
JSON while polling by catching parse failures (or skipping the last non-empty
line when it looks incomplete) and retrying until enough valid RouterLaunchLog
entries are available, instead of letting a SyntaxError abort the function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: deb45d15-c6cc-4aa2-91b7-7962436a13f1

📥 Commits

Reviewing files that changed from the base of the PR and between e940b4a and 80e3b51.

📒 Files selected for processing (3)
  • src/lib/onboard/model-router.ts
  • test/onboard-model-router.test.ts
  • test/support/model-router-process-test-helpers.ts

@cv

cv commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Final-head verification for 2955d8152 is complete.

  • CI: 40 passed, 0 failed, 2 intentionally skipped.
  • Matched shard-3 artifact: test/onboard-model-router.test.ts passed 8/8. File result improved from 14,005.160ms to 404.958ms (97.11% lower, 34.58× faster); collection plus tests improved from 14,054.999ms to 961.167ms (93.16% lower).
  • Live E2E: model-router-provider-routed-inference and cloud-onboard both passed in run 28883447759. The final commit changes only test-log parsing.
  • Normal pre-commit, commit-msg, and pre-push hooks passed; all four commits are GitHub-verified.

Automated-review disposition:

  • CodeRabbit partial-JSONL race: fixed in 2955d8152 by waiting for the newline-terminated record while continuing to fail on corrupt complete records. The earlier polling-loop deduplication note is intentionally left as a low-value style nit because the helpers have different results and termination behavior.
  • GPT fallback-fingerprint warning: the direct injected-null test intentionally preserves the pre-existing compatibility branch. Production fingerprinting first uses git and then hashes a readable source tree, so a real null state requires the source to be unavailable/unreadable; synthesizing that as an installation contract would be platform-sensitive. The real production provisioner is separately covered through Python discovery, venv creation, pip installation, managed-command selection, and persisted git fingerprinting.
  • Nemotron PRA-5/PRA-6: production supplies a trusted repo-owned router path, and symlinks are hashed via readlinkSync as link text rather than dereferenced, so the reported path-escape model does not apply.
  • PRA-7/PRA-9/PRA-10/PRA-11: these are moved, unchanged error policies; failed submodule initialization is surfaced by the subsequent package-readiness check. Diagnostic enhancements are not required for this performance refactor.
  • PRA-12: command -v receives the hardcoded model-router value as positional $1; there is no shell interpolation of caller-controlled text.
  • PRA-8 and the derived negative-test requests depend on those inapplicable threat models. PRA-13/PRA-14 are non-blocking dependency-injection/helper-organization suggestions.

Independent final-diff review and final-head CodeRabbit review found no remaining actionable correctness, security, credential-handling, typing, performance, or test-isolation issues.

@cv
cv merged commit 0fc3a72 into main Jul 7, 2026
50 checks passed
@cv
cv deleted the codex/perf-in-process-onboard-model-router branch July 7, 2026 17:34
@wscurran wscurran added area: ci CI workflows, checks, release automation, or GitHub Actions area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow area: routing Request routing, policy routing, model selection, or fallback logic refactor PR restructures code without intended behavior change labels Jul 7, 2026
Hadar301 pushed a commit to Hadar301/NemoClaw-OpenShift that referenced this pull request Jul 12, 2026
<!-- markdownlint-disable MD041 -->
## Summary

Replace five unit-shaped Model Router onboarding child-process tests
with direct typed boundaries while retaining a lightweight real process
contract. Extract managed-command provisioning so route, installation,
reuse, fingerprint, and launch behavior can be tested without repeatedly
loading the full onboarding graph or waiting on real health intervals.

## Related Issue

Refs NVIDIA#6245

## Changes

- Extract Model Router command discovery, managed-venv installation, and
source fingerprinting into a dependency-light provisioner while
preserving the existing `model-router.ts` exports.
- Give `startModelRouter` an immutable per-call dependency seam with
unchanged production defaults.
- Replace five source-loader child drivers with eight direct tests
covering routed-provider wiring, managed command
installation/reuse/refresh, fallback fingerprints, production adapter
composition, and real proxy launch arguments.
- Retain one lightweight real fake-router process contract for config
generation, detached launch, credential environment filtering, PID
liveness, and cleanup.
- Reduce the matched CI file result from 14,005.160ms on current main to
404.958ms on the final head (97.11% lower, 34.58× faster); collection
plus tests fell from 14,054.999ms to 961.167ms (93.16% lower).

## 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)

## Quality Gates
<!-- Check exactly one tests line and one docs line. Check other lines
when applicable. Add every requested justification or approval
reference. -->
- [x] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [ ] Tests not applicable — justification:
- [ ] Docs updated for user-facing behavior changes
- [x] Docs not applicable — justification: internal module extraction
and test dependency seams only; Model Router provider selection,
credentials, ports, managed virtual environment, startup behavior, and
failure semantics are unchanged.
- [x] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [x] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification: independent final-diff
review found no actionable correctness, security, credential-handling,
typing, or test-isolation findings; the retained process contract
verifies filtered credential delivery and cleanup.
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Verification
<!-- Check each applicable item only when supported by the requested
evidence. Run targeted tests once per relevant change set and rerun
after later edits or hook autofixes that can affect the tested behavior.
Do not rerun hook-covered checks. -->
- [x] PR description includes the DCO sign-off declaration and every
commit appears as `Verified` in GitHub
- [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or
`npm run check:diff` passed when hooks were skipped or unavailable
- [x] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — The target file passes 8/8 locally and
on final-head CI; related Model Router/routed-provider suites and 2/2
Model Router E2E-support tests also passed.
- [x] Required live E2E targets passed —
`model-router-provider-routed-inference` and `cloud-onboard` both passed
in run 28883447759; the final follow-up changes only test-log parsing.
- [ ] Applicable broad gate passed — `npm test` for broad
runtime/test-harness changes; `npm run check` for repo-wide
validation/coverage changes — command/result:
- [x] Quality Gates section completed with required justifications or
waivers
- [x] No secrets, API keys, or credentials committed
- [ ] `npm run 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)

---
<!-- DCO sign-off is required in this PR description, and every commit
must appear as Verified in GitHub. Run: git config user.name && git
config user.email -->
Signed-off-by: Carlos Villela <cvillela@nvidia.com>


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

* **New Features**
* Added managed Model Router command provisioning that reuses an
existing managed venv install when it matches the current router source,
and provisions it when needed.
* Refactored router startup to support dependency-injected health checks
and proxy spawning.
* **Bug Fixes**
* Improved “currentness” detection using source fingerprints, with a
safe fallback when git-based fingerprinting isn’t available.
* More consistent readiness, termination, and retry behavior during
startup.
* **Tests**
* Reworked Model Router onboarding tests to run in-process with a
command harness, plus new process/log test helpers and fixtures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ci CI workflows, checks, release automation, or GitHub Actions area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow area: routing Request routing, policy routing, model selection, or fallback logic refactor PR restructures code without intended behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants