Skip to content

Fix RSC install and doctor diagnostics#4213

Merged
justin808 merged 19 commits into
mainfrom
jg-codex/rsc-doctor-install-blockers
Jun 26, 2026
Merged

Fix RSC install and doctor diagnostics#4213
justin808 merged 19 commits into
mainfrom
jg-codex/rsc-doctor-install-blockers

Conversation

@justin808

@justin808 justin808 commented Jun 25, 2026

Copy link
Copy Markdown
Member

Why

This batch covers the RSC install/doctor blockers filed by Justin in #4198, #4199, #4200, and #4204.

RSC setup failures were too easy to misdiagnose: react-on-rails-rsc could be installed on a React line that its own peer dependencies did not support, a stale latest vs next npm track could be missed, and an empty or dev-server-backed client manifest could surface as a generic React Client Manifest/bundler failure.

What changed

  • Adds doctor validation for installed react-on-rails-rsc peer requirements against installed react and react-dom, including FORMAT=json coverage under react_server_components.
  • Warns when installed react-on-rails-rsc is behind newer next or rc npm dist-tags.
  • Adds doctor diagnostics for missing, URL-backed, invalid, missing-metadata, or empty RSC client manifests, with bin/dev static and clean rebuild guidance.
  • Enriches Pro RSC React Client Manifest lookup failures with stale/empty/cross-version manifest guidance.
  • Adds generator test coverage proving the current stable RSC pairing remains intentional: React/React DOM on the ~19.0.4 policy and react-on-rails-rsc@19.0.5.
  • Adds a scoped Enhancement: dedicated RSC health check (react_on_rails:doctor --rsc) #4204 investigation comment for the remaining artifact/freshness and RSC-only doctor-mode work: Enhancement: dedicated RSC health check (react_on_rails:doctor --rsc) #4204 (comment)

Issue handling

Verification

Coordinator and QA lane evidence:

  • bundle exec rspec spec/lib/react_on_rails/doctor_spec.rb --format progress -> 286 examples, 0 failures.
  • bundle exec rspec spec/react_on_rails/generators/install_generator_spec.rb:2769 spec/react_on_rails/generators/install_generator_spec.rb:3139 --format progress -> 2 examples, 0 failures.
  • bundle exec rspec spec/react_on_rails/generators/js_dependency_manager_spec.rb:702 --format progress -> 3 examples, 0 failures on the Generator pins stale react-on-rails-rsc latest (19.0.5) → RSC demo doesn't server-render on React 19.2 #4199 lane.
  • pnpm --filter react-on-rails-pro run test:rsc -> 6 suites, 18 tests passed.
  • pnpm --filter react-on-rails-pro run type-check -> pass.
  • pnpm --filter react-on-rails-pro run build -> pass.
  • BUNDLE_GEMFILE=../Gemfile bundle exec rubocop lib/react_on_rails/doctor.rb spec/lib/react_on_rails/doctor_spec.rb spec/react_on_rails/generators/install_generator_spec.rb -> no offenses.
  • git diff --check -> pass.
  • pnpm exec prettier --check ... on touched TS files -> pass.
  • Targeted ESLint exited 0; the new RSC test file is ignored by the repo config with a warning.
  • script/ci-changes-detector origin/main routes this as broad: Ruby core, generator, Pro TS, and uncategorized/full-suite safety.

Flagship demo decision

No flagship demo update is needed. No recommended Pro/RSC defaults changed. Live npm metadata still has react-on-rails-rsc@latest = 19.0.5 with React peers ^19.0.4; next = 19.2.0-rc.4 peers on React ^19.2.7.

Risks / deferred work

  • No live bin/dev / bin/dev static browser smoke was run in this batch. Coverage is focused doctor/generator/Pro RSC unit evidence plus the required QA lane.
  • claude-review is not installed in this environment, so the independent review gate used here was the dedicated QA subagent plus local validation.
  • Enhancement: dedicated RSC health check (react_on_rails:doctor --rsc) #4204 freshness design remains UNKNOWN until a follow-up chooses authoritative build metadata or a tested heuristic for cross-manifest consistency.

Closeout update

  • Latest reviewed head: eb44230070e4065edc393a2bdba4cf75b32bff0a.
  • Additional local validation on the final review-fix batches: Ruby syntax checks; bundle exec rspec spec/lib/react_on_rails/doctor_spec.rb -> 305 examples, 0 failures; focused RuboCop -> no offenses; pnpm exec prettier --check packages/react-on-rails-pro/src/handleErrorRSC.ts -> pass; pnpm --filter react-on-rails-pro test:rsc -- --runTestsByPath tests/handleErrorRSC.rsc.test.ts -> 6 suites, 18 tests passed; git diff --check -> pass.
  • Review thread closeout: trusted Claude threads through 2026-06-26T04:47:15Z were replied to and resolved; latest unresolved-thread sweep on eb4423007 found 0 unresolved threads.
  • Merge ledger: script/pr-merge-ledger 4213 --strict --pretty --changelog-classification changelog_present at 2026-06-26T04:53:21Z reported complete_allowed: true; final ledger will be rerun after current-head hosted checks/review finish.
  • Security preflight note: github-actions[bot] comment content is ignored as untrusted per maintainer acknowledgement; trusted review comments only were used as actionable input.

Merge authority

User-authorized auto-merge when gates pass. Maintainer instruction in Codex thread on 2026-06-26: proceed while ignoring the untrusted GitHub Actions bot comment content, and auto-merge when gates pass.

Summary by CodeRabbit

  • New Features
    • Enhanced React Server Components setup diagnostics: React/ReactDOM peer compatibility validation for react-on-rails-rsc, npm dist-tag awareness for potentially stale installs, and React Client Manifest checks with static-mode recovery guidance.
  • Bug Fixes
    • Improved Pro React Server Components render error messaging by appending targeted help when the React Client Manifest lookup fails.
  • Tests
    • Expanded RSC doctor and Pro error-handling test coverage for manifest edge cases, dist-tag behavior, and peer/version validation.
  • Documentation
    • Updated the changelog with the new Pro RSC diagnostic behavior.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Updated RSC diagnostics across the doctor, Pro render-error handling, generator dependency checks, and test coverage. The doctor now validates react-on-rails-rsc compatibility and client-manifest state; the Pro renderer appends stale-manifest guidance; tests cover the new error, JSON, and dependency paths.

Changes

RSC diagnostics and validation

Layer / File(s) Summary
Render error guidance
CHANGELOG.md, packages/react-on-rails-pro/src/handleErrorRSC.ts, packages/react-on-rails-pro/tests/handleErrorRSC.rsc.test.ts
Adds client-manifest cleanup guidance to the Pro RSC render error path, records the change in the changelog, and verifies the decoded error stream.
Doctor setup and compatibility
react_on_rails/lib/react_on_rails/doctor.rb
Adds RSC constants, invokes the client-manifest check during setup, splits legacy and compatibility version checks, and adds package lookup, peer-range, dist-tag, and manifest validation logic.
Doctor specs
react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
Covers RSC peer compatibility errors, dist-tag warnings, package-resolution validation, JSON output, range parsing, and client-manifest warning cases.
Generator dependency expectations
react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
Extends RSC dependency assertions to cover React and ReactDOM version ranges and the pinned react-on-rails-rsc version, including tilde range handling.

Possibly related issues

Possibly related PRs

  • shakacode/react_on_rails#3926: Both PRs touch the install generator test assertions around npm version-range expectations, including the shared dependency-assertion path.

Sequence Diagram(s)

sequenceDiagram
  participant Doctor as ReactOnRails::Doctor
  participant Resolver as Node require.resolve
  participant Npm as npm view
  participant Utils as ReactOnRailsPro::Utils
  participant Manifest as react-server-client-manifest.json

  Doctor->>Resolver: resolve installed package.json files
  Resolver-->>Doctor: installed versions and specs
  Doctor->>Npm: view react-on-rails-rsc dist-tags
  Npm-->>Doctor: next / rc tags
  Doctor->>Utils: resolve RSC client manifest path
  Utils-->>Doctor: local path or dev-server URL
  Doctor->>Manifest: read filePathToModuleMetadata
  Manifest-->>Doctor: client references or missing / empty / invalid
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

enhancement, runtime-fix, review-needed, full-ci, P2

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main RSC install and doctor diagnostic fixes.
Linked Issues check ✅ Passed The PR implements the requested doctor compatibility checks, stale dist-tag warnings, JSON output, and manifest diagnostics for #4198 and #4200.
Out of Scope Changes check ✅ Passed The additional refactors and tests support the same RSC diagnostics work and do not appear unrelated to the stated objectives.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jg-codex/rsc-doctor-install-blockers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@justin808

Copy link
Copy Markdown
Member Author

+ci-status

@justin808

Copy link
Copy Markdown
Member Author

+ci-run-hosted

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
@claude

claude Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #4213: Fix RSC install and doctor diagnostics

Overall this is a well-structured batch. The decomposition into focused helper methods is clean, the test coverage is meaningful (real tmpdir setups rather than mocked stubs for the core semver logic), and the handleErrorRSC.ts enrichment pattern is idiomatic. A few issues to address before merge:


🔴 Security — command injection surface in installed_package_json

package_name is interpolated directly into a Node.js expression run by node -e. All current callers use compile-time constants (RSC_PACKAGE_NAME, "react", "react-dom"), so there is no live exploit, but the function silently accepts arbitrary strings. A future caller that passes a name derived from a package.json key could execute arbitrary Node code.

See inline comment at doctor.rb:3877. Fix: validate the name against a package-name character class before interpolating.


🟡 Bug — npm hyphen ranges always evaluate to false

npm_range_clause_satisfied? parses comparators with a scan regex. For input "1.0.0 - 2.0.0" (hyphen range, meaning >=1.0.0 <=2.0.0), the scan extracts 1.0.0 and 2.0.0 without any operator, which npm_comparator_satisfied? treats as equality (=). The result is always false for any version not simultaneously equal to both bounds — i.e. always false.

Hyphen ranges are uncommon in React peer deps, but the silent wrong result could mislead users. The minimum fix is a # NOTE: hyphen ranges not supported comment so the limitation is visible; a fuller fix detects and converts them.

See inline comment at doctor.rb:3722.


🟡 Bug — npm_tilde_upper_bound diverges from npm spec for single-component versions

npm_tilde_upper_bound("19") returns "19.1.0" instead of "20.0.0". The identical helper in install_generator_spec.rb is written correctly with the version.segments.length > 1 guard. Not a problem for any current peer-dep range, but the production and test implementations are inconsistent.

See inline comment at doctor.rb:3771–3774.


🟡 Performance / reliability — live npm registry call on every doctor run

rsc_dist_tags calls npm view … --json on every invocation. The 5-second timeout is sensible, but:

  • Air-gapped / proxy environments silently skip the check with no user-visible indication.
  • CI runs that invoke rails doctor repeatedly pay the network cost each time.

Suggested: memoize with @rsc_dist_tags_cache and emit an info line on timeout/failure instead of swallowing it silently.

See inline comment at doctor.rb:3703.


✅ What looks good

  • addRSCClientManifestGuidance in handleErrorRSC.ts is idempotent (guarded with includes('[React on Rails Pro RSC diagnostic]')), which prevents double-injection.
  • The check_rsc_package_compatibility_if_present / check_rsc_package_peer_compatibility tri-state return design (nil = skip, false = fail already recorded, true = pass) is clear and avoids double-reporting.
  • rsc_client_manifest_file_path guards with defined? + respond_to? before calling into Pro — correct defensive check.
  • The new install_generator_spec.rb assert_rsc_dependency_requirements helper is a clean extraction that makes the intent of both call sites obvious.
  • Test contexts use real Dir.mktmpdir + real filesystem installs; the only stub is the npm view dist-tag network call, which is the right tradeoff.

@github-actions

Copy link
Copy Markdown
Contributor

CI Status

Head SHA: 1b68c71d72f6
Changed files: 6
Docs-only heuristic (matches ci-changes-detector metadata paths): no
ready-for-hosted-ci label: absent
force-full-hosted-ci label: absent
Current hosted-CI waiver: not present for this SHA

Only the required gate is active unless hosted CI is requested.

@claude

claude Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Code review posted inline. Summary: (1) Security: installed_package_json interpolates package_name into a Node.js script — use process.argv to prevent injection. (2) rsc_dist_tags makes a live npm registry call silently; add user-visible messaging or an opt-in flag. (3) Timeout::Error no longer inherits from StandardError in Ruby 3.x so the rescue in rsc_dist_tags will miss timeouts. (4) Tri-state nil/true/false return from check_rsc_package_peer_compatibility is subtle. (5) check_rsc_client_manifest test block is missing coverage for URL, not-found, and missing-key branches. (6) Minor: RSC_CLIENT_MANIFEST_CLEANUP_PATHS and handleErrorRSC.ts diverge on the packs path glob.

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/spec/lib/react_on_rails/doctor_spec.rb
Comment thread packages/react-on-rails-pro/src/handleErrorRSC.ts Outdated
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
react-on-rails/client bundled (gzip) 63.5 KB (0%)
react-on-rails/client bundled (gzip) (time) 63.5 KB (0%)
react-on-rails/client bundled (brotli) 54.51 KB (0%)
react-on-rails/client bundled (brotli) (time) 54.51 KB (0%)
react-on-rails-pro/client bundled (gzip) 64.3 KB (0%)
react-on-rails-pro/client bundled (gzip) (time) 64.3 KB (0%)
react-on-rails-pro/client bundled (brotli) 55.27 KB (0%)
react-on-rails-pro/client bundled (brotli) (time) 55.27 KB (0%)
registerServerComponent/client bundled (gzip) 75.31 KB (0%)
registerServerComponent/client bundled (gzip) (time) 75.31 KB (0%)
registerServerComponent/client bundled (brotli) 64.85 KB (-0.04% 🔽)
registerServerComponent/client bundled (brotli) (time) 64.85 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) 68.03 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) (time) 68.03 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) 58.42 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) (time) 58.42 KB (0%)

@justin808

Copy link
Copy Markdown
Member Author

Address Review Summary

Scan scope: full PR history for #4213; no previous address-review summary was present.

Addressed in 738dd58ff, fa024feda, and 43375e4b7:

  • Hardened package resolution against JavaScript injection and added invalid package-name coverage.
  • Fixed npm range handling for hyphen ranges and single-component tilde ranges.
  • Memoized npm dist-tag lookup, surfaced lookup/skip info, and switched to npm's --fetch-timeout=5000 instead of Ruby Timeout.timeout around the subprocess.
  • Simplified RSC peer compatibility helper return values to boolean-only.
  • Added RSC client manifest coverage for dev-server URL, missing file, and missing metadata key branches.
  • Linked the Ruby cleanup-path list and Pro TypeScript guidance with sync comments.

Verified and dispositioned: the claim that Timeout::Error no longer inherits from StandardError was checked against the local Ruby runtime and was false here, but the underlying subprocess-timeout concern was addressed by removing the Ruby timeout wrapper.

Deferred work: none from this review pass. A future explicit --check-updates flag for networked doctor checks would be a product/API scope decision, not required for this PR's current blocker fix.

@justin808

Copy link
Copy Markdown
Member Author

+ci-run-hosted

@github-actions github-actions Bot added the ready-for-hosted-ci Run optimized hosted GitHub CI for this PR label Jun 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hosted CI Requested

Triggered 9 workflow(s) for 43375e4b7b0d.
Mode: optimized hosted CI (path-selected by script/ci-changes-detector).
Added ready-for-hosted-ci, so future commits will keep running optimized hosted CI until +ci-stop-hosted is used.

View progress in the Actions tab.

@justin808

Copy link
Copy Markdown
Member Author

@claude review this PR

@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overall this is a well-structured addition. The RSC diagnostics address real pain points, the subprocess lifecycle management is careful, and the test coverage is thorough (peer compat, dist-tag staleness, manifest states, process timeout/cleanup).

Three specific findings below (two inline, one here):

installed_package_json fallback to flat node_modules/ (doctor.rb:4206)

When Node resolution fails, the code falls back to reading node_modules/#{package_name}/package.json directly. The comment correctly flags the limitation: a stale orphaned directory can be read silently. For peer dependency validation this is the most risky path — if npm's resolution is broken, the fallback can report the wrong installed version and mark an incompatible pairing as compatible, precisely the failure mode this PR is trying to catch.

Suggestion: demote the fallback result (i.e., emit a checker.add_info note that resolution fell back to a flat-layout scan) so users see when this happens. Alternatively, consider whether the fallback is worth keeping at all — a "could not resolve" error is more actionable than a silent wrong-version read.

Minor positives worth calling out

  • The PACKAGE_NAME_PATTERN allowlist before Node resolution is exactly right — stops injection without a shell.
  • pgroup: true + group-kill in signal_rsc_dist_tag_process ensures npm children don't become orphans on timeout.
  • The RSC_DIAGNOSTIC_SENTINEL double-append guard is a clean pattern.
  • Custom npm semver range parser is thorough; the prerelease-tuple rule matches npm spec behavior.

Comment thread packages/react-on-rails-pro/src/handleErrorRSC.ts Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread packages/react-on-rails-pro/tests/handleErrorRSC.rsc.test.ts
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #4213 (Fix RSC install and doctor diagnostics)

Overall assessment

Solid, well-scoped batch. The security posture around package-name injection is tight (PACKAGE_NAME_PATTERN + array-form Process.spawn/Open3.capture3 everywhere), the custom npm semver implementation is tested against the right edge cases (prerelease exclusion, caret/tilde/hyphen/x-ranges, || compositing), and the subprocess management in capture_rsc_dist_tags handles the timeout/cleanup/double-reap scenarios correctly. The RSC_DIAGNOSTIC_SENTINEL guard in addRSCClientManifestGuidance prevents double-decoration cleanly.

Three findings below, one actionable now and two deferred.


1. Missing passthrough test in handleErrorRSC.rsc.test.ts — recommend adding before merge

See inline comment at line 60. The test suite only covers the match path. A bug that appended guidance to all errors would not be caught by the current tests.

2. Warning vs. info for absent react_client_manifest_file_path method — recommend fixing before merge

See inline comment at line 4266 of doctor.rb. When the open-source gem is newer than the Pro gem installed alongside it, ReactOnRailsPro::Utils exists but won't respond to react_client_manifest_file_path. The current code emits a :warning with message "cannot verify client references" — which is technically true but gives the user no actionable guidance. A Pro-API-version mismatch is a different root cause from a genuinely missing manifest and deserves a different (lower-severity, differently-worded) message.

3. installed_package_json flat-layout fallback when Node exits non-zero — deferred

See inline comment at line 4207 of doctor.rb. When node -e ... fails (Node not on PATH, CI permission issue), the code falls back to a flat node_modules/ path regardless. This is safe (outer rescue guards it), but it can silently read stale orphaned package.json files in environments where Node isn't reachable. Worth a follow-up comment or a separate PR; not a blocker here.


Minor observations (no action needed)

  • npm range parser: npm_comparators_range_result scan regex correctly captures multi-char operators (>=, <=) before single-char ones in the alternation. Prerelease-exclusion via npm_prerelease_allowed_by_versions? correctly follows the npm spec (prerelease X.Y.Z-pre matches a range only if the range itself has a prerelease on the same X.Y.Z tuple). Tests confirm the right cases.
  • Process group cleanup: pgroup: true + Process.kill(signal, -pid) correctly propagates signals to npm child processes. The Errno::EINVAL rescue in signal_rsc_dist_tag_process handles the "process group gone" case cleanly.
  • @rsc_dist_tags_cache keyed by package_root: Correct. A second check_rsc_react_version call on the same doctor instance hits the cache, which the test at doctor_spec.rb:1169 explicitly verifies.
  • parsed_package_json cache: Fine for a single diagnostic run. If package.json changes mid-run the cache serves stale data, but that's an acceptable trade-off for a CLI tool.

@justin808 justin808 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Submitting pending Codex review replies from the previous fix batch.

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb

@justin808 justin808 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Submitting pending Codex review replies from the previous fix batch.

Comment thread packages/react-on-rails-pro/src/handleErrorRSC.ts Outdated
@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #4213

Overall this is solid work. The subprocess management is carefully built with proper pipe cleanup, the package name validation is tight, and the test coverage is thorough. A few things worth flagging:


Errno::EINVAL rescue in signal_rsc_dist_tag_process

Process.kill(signal, -pid) with valid signal names ("TERM", "KILL") should not raise EINVAL on Linux or macOS — that error code is reserved for invalid signal values (not bad PIDs). The cases where a negative-PID group kill can fail are ESRCH (no such group) and EPERM (no permission). The current rescue:

rescue Errno::ESRCH, Errno::EINVAL
  Process.kill(signal, pid)

…silently falls back to the direct PID kill on EINVAL, but EINVAL from a valid signal+pgroup combo would indicate something unusual about the environment. More importantly, Errno::EPERM is not caught here, so a permission failure propagates out of the fallback too. If the fallback is needed for compatibility with platforms that reject kill(-pid), a comment explaining which platform/Ruby version triggers EINVAL would make the intent clearer.


Wildcard fast-path skips prerelease filtering

In npm_range_clause_satisfied?, the fast path:

return true if range_clause.match?(/\A[~^]?\s*[*xX](?:\.[*xX]){0,2}\z/)

returns true for * regardless of whether version is a prerelease. node-semver's default behavior (without includePrerelease) excludes prereleases from wildcard ranges — semver.satisfies("1.0.0-rc.1", "*") is false. The x-range path correctly gates on return false if npm_prerelease(version).present?, but the fast path bypasses it.

In practice, packages almost never use * as a peer dependency range, so the impact is very low. Worth noting for long-term npm compliance.


Doctor run latency

The npm dist-tag lookup polls with sleep 0.05 up to the 5-second deadline, which means every rails react_on_rails:doctor invocation that has network access blocks for however long npm takes (or up to 5s). The opt-out env var REACT_ON_RAILS_SKIP_NPM_DIST_TAG_CHECK is documented in code but not mentioned in CLI output unless the check is actually skipped. Depending on the audience (e.g., CI environments where network calls to npm are flaky), users might not know to set this. Adding a note to the doctor startup output like "Set REACT_ON_RAILS_SKIP_NPM_DIST_TAG_CHECK=1 to skip the npm network check" could help discoverability.


Everything else looks well-considered: the package name pattern correctly blocks path traversal in the File.join fallback, process_reaped = true after wait_for_rsc_dist_tag_process is correctly placed so the ensure cleanup is a no-op on the normal path, the RSC_DIAGNOSTIC_SENTINEL double-append guard is clean, and the generator test coverage for the ~19.0.4 React pin is a good addition to lock down the stable RSC pairing.

Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/doctor.rb Outdated
@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #4213 (Fix RSC install and doctor diagnostics)

Overall the PR is well-structured with solid test coverage. A few observations below, roughly priority-ordered.


Custom npm semver implementation (maintenance risk)

doctor.rb now ships ~200 lines of custom npm semver range parsing (npm_range_satisfied? and its helpers). This covers caret, tilde, x-range, hyphen-range, comparator sets, ||-or, and prerelease exclusion — all correctly per the test matrix. The tests are thorough.

The risk is long-term maintenance: npm semver has edge cases around ^0.0.x, partial versions in hyphen ranges, prerelease ordering against mixed numeric/alphanumeric identifiers, and >= ranges that cross prerelease boundaries. If a future RSC peer uses an unusual range shape (e.g. >=19.2.0-rc.0 <20.0.0-0, 1.2 - 1.3, or a range with build metadata) there may be silent misfires.

Worth considering: could node -e "require('semver').satisfies(process.argv[1], process.argv[2])" <version> <range> be invoked via Open3 to delegate range evaluation to the authoritative npm semver implementation? Node is already a declared dependency and is already used in installed_package_json. The overhead is comparable to the existing resolve call. If staying with the custom implementation, adding a comment pointing to the npm semver spec URL would help future maintainers.


Subprocess management in capture_rsc_dist_tags

The pipe/thread/SIGTERM/SIGKILL sequence is correct. process_reaped = true is set after wait_for_rsc_dist_tag_process returns, which prevents the ensure block from attempting a second terminate_rsc_dist_tag_process call regardless of whether the wait timed out. The pgroup: true + negative-pid SIGTERM fallback is the right pattern for npm subprocesses that spawn their own children. The timeout test with stub_const verifies the kill path. No issue here, just noting it was verified.


fetch_rsc_dist_tags always emits the "Checking…" info line

checker.add_info("... Checking #{RSC_PACKAGE_NAME} npm dist-tags ...") at line 3761 fires before the HTTP fetch and before any comparison. If the installed version is already the latest across all dist-tags, users still see "Checking npm dist-tags to flag stale RSC pins" with no follow-up warning. In a verbose doctor run this is noise. Minor — could be deferred to only log when a warning will actually be emitted, or dropped entirely (the per-tag warning already explains the source).


PACKAGE_NAME_PATTERN injection guard

The pattern (\A(?:@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*\z) is tight: no uppercase, no path separators, no shell metacharacters. Passing package_name as process.argv[1] rather than template-substituting into the script string is the right call. The flat-layout fallback uses File.join with a validated name, preventing traversal. Security posture here is solid.


Minor: tag_version interpolated from npm JSON into warning messages

tag_version (from JSON.parse(stdout)) is interpolated verbatim into add_warning strings and into the suggested npm view command shown to the user. If a registry returned an adversarial version string (e.g. one with newlines or ANSI escapes), it would appear in the doctor output. This is display-only (not executed), but a tag_version.strip or a short format-safety check would be belt-and-suspenders.

Comment thread packages/react-on-rails-pro/src/handleErrorRSC.ts
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
Comment thread react_on_rails/lib/react_on_rails/doctor.rb
@justin808

Copy link
Copy Markdown
Member Author

Final closeout evidence

Head: 20220c07a74841f457a58ae494aa0f3affb8e198

Local validation used before final push:

  • cd react_on_rails && bundle exec rspec spec/lib/react_on_rails/doctor_spec.rb — 309 examples, 0 failures
  • cd react_on_rails && BUNDLE_GEMFILE=../Gemfile bundle exec rubocop lib/react_on_rails/doctor.rb spec/lib/react_on_rails/doctor_spec.rb — no offenses
  • pnpm exec prettier --check packages/react-on-rails-pro/src/handleErrorRSC.ts packages/react-on-rails-pro/tests/handleErrorRSC.rsc.test.ts — passed
  • pnpm --filter react-on-rails-pro test:rsc -- --runTestsByPath tests/handleErrorRSC.rsc.test.ts — passed
  • git diff --check — passed
  • pre-commit and pre-push hooks on final commits — passed

Hosted/merge gates:

  • pr-ci-readiness 4213READY, no failing or pending required checks
  • Full hosted rollup on current head — green/skipped only, including generator RSpec, Pro package/integration, node-renderer e2e/rspec, CodeQL, lint, markdown, bundle size, Playwright, and Claude review
  • Current-head review threads — 0 unresolved
  • script/pr-merge-ledger 4213 --strict --pretty --changelog-classification changelog_presentcomplete_allowed: true, no violations/unknowns

Review handling:

  • Trusted Claude review threads were fixed or explicitly [auto-deferred] when optional and after the final-candidate debounce point.
  • The github-actions[bot] comment content was ignored as untrusted per maintainer instruction.

QA/coordination:

  • Required QA lane qa/agent-b2-qa was satisfied earlier in the batch.
  • Flagship demo decision: no external demo update needed because this PR does not change recommended Pro/RSC defaults, pins, or setup path.

Merge authority: maintainer authorized auto-merge once gates pass.

@justin808 justin808 added this pull request to the merge queue Jun 26, 2026
Merged via the queue into main with commit 58ab0dd Jun 26, 2026
61 checks passed
@justin808 justin808 deleted the jg-codex/rsc-doctor-install-blockers branch June 26, 2026 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-hosted-ci Run optimized hosted GitHub CI for this PR

Projects

None yet

1 participant