Skip to content

feat: add REACT_ON_RAILS_BASE_PORT for concurrent worktree port management#3142

Open
justin808 wants to merge 34 commits intomainfrom
jg/concurrent-worktree-ports
Open

feat: add REACT_ON_RAILS_BASE_PORT for concurrent worktree port management#3142
justin808 wants to merge 34 commits intomainfrom
jg/concurrent-worktree-ports

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Apr 15, 2026

Summary

  • Adds a tool-agnostic REACT_ON_RAILS_BASE_PORT env var that derives all service ports from a single base value (Rails = base+0, webpack = base+1, renderer = base+2)
  • Also recognizes CONDUCTOR_PORT as a fallback, so Conductor.build works out of the box
  • Makes all Procfiles use ${PORT:-3000} and ${RENDERER_PORT:-3800} patterns instead of hardcoded values
  • Propagates renderer port via RENDERER_PORT and REACT_RENDERER_URL env vars for automatic Pro Node Renderer configuration

This enables coding agent tools (Conductor.build, Codex, Quad Code, etc.) to run multiple worktrees concurrently without port conflicts — just set one env var and everything works.

Changes

Core logic:

  • PortSelector: new base port priority tier (checked before explicit env vars and auto-detect)
  • ServerManager.configure_ports: sets RENDERER_PORT and REACT_RENDERER_URL when base port is active

Procfile templates (all now use env var fallbacks):

  • OSS: Procfile.dev, Procfile.dev-prod-assets, Procfile.dev-static-assets
  • Pro: Procfile.dev, Procfile.prod, Procfile.static
  • Generator (pro_setup.rb): uses ${RENDERER_PORT:-3800} in generated Procfile lines

Documentation:

  • .env.example: documents base port, RENDERER_PORT, and REACT_RENDERER_URL
  • process-managers.md: new "Coding Agent / CI Integration" and "Manual Worktree Port Setup (Pro)" sections

Test plan

  • PortSelector unit tests: base port derivation, CONDUCTOR_PORT fallback, priority ordering, out-of-range fallback, nil renderer when no base port
  • RuboCop passes on all changed files
  • All pre-commit and pre-push hooks pass
  • CI passes
  • Manual test: REACT_ON_RAILS_BASE_PORT=4000 bin/dev prints correct port assignments
  • Generator tests pass (updated assertions for new Procfile pattern)

🤖 Generated with Claude Code


Note

Medium Risk
Medium risk because it changes bin/dev port/env handling (including unconditional overrides in base-port mode) and bin/dev kill targeting, which could disrupt local dev setups if env vars or remote renderer URLs are configured unexpectedly.

Overview
Adds a deterministic base-port mode to bin/dev: when REACT_ON_RAILS_BASE_PORT (or CONDUCTOR_PORT) is set, Rails/webpack/node-renderer ports are derived as base+0/+1/+2 and applied consistently across development, static, and prod modes, with warnings when pre-set env values are overridden.

Improves dev reliability by cleaning up copied/stale Overmind sockets and tmp/pids/server.pid (including cross-directory PID detection), expanding bin/dev kill to target base-port-derived ports and renderer ports when applicable, and normalizing invalid/whitespace port env vars.

Standardizes Pro renderer configuration on REACT_RENDERER_URL (warns on legacy RENDERER_URL usage), auto-derives REACT_RENDERER_URL from RENDERER_PORT when missing, and updates Procfiles/generator templates/docs/tests to use ${PORT:-…} / ${RENDERER_PORT:-…} defaults and the new env-var conventions.

Reviewed by Cursor Bugbot for commit b1cc0bb. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Dev ports (Rails, webpack, renderer) can be driven from a single base-port env var, deterministically deriving offsets.
  • Behavior Changes

    • Procfiles and examples now honor PORT/RENDERER_PORT via shell-defaults so runtime ports can be overridden.
    • Dev tooling forwards renderer env vars to spawned processes and synchronizes renderer URL/port when derived.
  • Documentation

    • Added guidance on base-port usage, precedence, bounds, and manual Pro renderer setup.
  • Tests

    • Expanded coverage for base-port derivation, precedence, bounds, invalid values, and fallbacks.
  • Chores

    • Minor .gitignore addition.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 15, 2026

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

Adds deterministic base-port support via REACT_ON_RAILS_BASE_PORT (fallback CONDUCTOR_PORT), deriving Rails/webpack/renderer at offsets +0/+1/+2; surfaces renderer in port selection; updates Procfiles, templates, docs, process/env preservation, and specs to prefer environment-driven port values with shell-default expansion.

Changes

Cohort / File(s) Summary
Core Port Selection & Server Manager
react_on_rails/lib/react_on_rails/dev/port_selector.rb, react_on_rails/lib/react_on_rails/dev/server_manager.rb
Adds deterministic base-port mode using REACT_ON_RAILS_BASE_PORT (then CONDUCTOR_PORT) to derive { rails, webpack, renderer } = base+0/+1/+2; adds parsing, validation, offset constants, and branches ServerManager to apply base-derived envs or fall back to explicit/env-default logic with renderer synchronization.
Generators & Templates
react_on_rails/lib/generators/react_on_rails/pro_setup.rb, react_on_rails/lib/generators/react_on_rails/templates/base/base/.env.example
Generator output and .env.example document REACT_ON_RAILS_BASE_PORT and use shell-defaults for renderer (RENDERER_PORT=${RENDERER_PORT:-3800}); adds renderer example vars and mapping notes.
Process Env Preservation
react_on_rails/lib/react_on_rails/dev/process_manager.rb
Expanded ENV_KEYS_TO_PRESERVE to include RENDERER_PORT and REACT_RENDERER_URL so those vars are forwarded when running processes outside Bundler.
Procfile Fixtures (dummy / spec)
react_on_rails/spec/dummy/Procfile.dev, react_on_rails/spec/dummy/Procfile.*, react_on_rails_pro/spec/.../Procfile.*
Procfile entries now honor env-driven ports with shell-default fallbacks (e.g., -p ${PORT:-3000}, RENDERER_PORT=${RENDERER_PORT:-3800}) instead of hardcoded ports.
Tests / Specs
react_on_rails/spec/react_on_rails/dev/port_selector_spec.rb, react_on_rails/spec/react_on_rails/dev/server_manager_spec.rb, react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb, react_on_rails/spec/react_on_rails/support/...
Adds/expands specs covering base-port behavior, precedence (REACT_ON_RAILS_BASE_PORT over CONDUCTOR_PORT), out-of-range and invalid-value fallbacks, asserts no port probing when base is used, and updates generator expectations for shell-default renderer port.
Docs & Misc
docs/oss/building-features/process-managers.md, .gitignore
Documentation added/expanded describing base-port usage, precedence, and manual Pro worktree setup; .gitignore updated to ignore .gstack/.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as bin/dev
    participant PS as ReactOnRails::Dev::PortSelector
    participant SM as ReactOnRails::Dev::ServerManager
    participant Svc as Services (Rails, Webpack, Node-Renderer)

    Dev->>PS: select_ports()
    alt REACT_ON_RAILS_BASE_PORT detected
        PS->>PS: validate base_port (1..MAX_BASE_PORT)
        PS->>PS: derive rails=base+0, webpack=base+1, renderer=base+2
        PS-->>Dev: {rails, webpack, renderer}
    else CONDUCTOR_PORT detected
        PS->>PS: parse conductor as base
        PS->>PS: derive rails=base+0, webpack=base+1, renderer=base+2
        PS-->>Dev: {rails, webpack, renderer}
    else
        PS->>PS: use explicit envs or probe free ports
        PS-->>Dev: {rails, webpack, nil}
    end

    Dev->>SM: configure_ports(selected)
    alt selected.renderer present
        SM->>SM: set ENV["PORT"], ENV["SHAKAPACKER_DEV_SERVER_PORT"] (overwrite)
        SM->>SM: set ENV["RENDERER_PORT"], ENV["REACT_RENDERER_URL"]
        SM-->>Svc: processes inherit envs
    else
        SM->>SM: apply explicit/env-default PORT and dev-server using ||= 
        SM->>SM: set REACT_RENDERER_URL only if RENDERER_PORT present and URL unset
        SM-->>Svc: processes inherit envs (no renderer)
    end

    Svc->>Svc: bind services to assigned ports
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • AbanoubGhadban
  • alexeyr-ci2

Poem

🐰 I found a base port in the glen,
Rails, Webpack, Renderer — lined by ten,
Offsets hopped and ports took their place,
ENV whispers tied each little space,
I thump a drum — dev servers race 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: introducing REACT_ON_RAILS_BASE_PORT for managing concurrent worktree ports, which is the primary feature across all modified files.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 jg/concurrent-worktree-ports

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 and usage tips.

Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 15, 2026

Code Review

Overview

This PR adds a REACT_ON_RAILS_BASE_PORT env var (with a CONDUCTOR_PORT fallback) that lets coding-agent tools assign all service ports from a single value — Rails = base+0, webpack = base+1, Pro renderer = base+2. All Procfiles are standardized to use ${PORT:-N} patterns and generator output is updated to match. The design is clean and the scope is well-contained.

What Works Well

  • Priority chain is sensible — base port > explicit per-service vars > auto-detect. The BASE_PORT_ENV_VARS list makes it easy to add new tool-specific vars later.
  • No probing when base port is set — deterministic and fast, which is exactly what CI/agent environments need.
  • Test coverage is solid — all priority tiers, edge cases (out-of-range base, both vars set), and the renderer-nil path are covered. The around blocks cleanly save/restore env state.
  • Procfile standardization is a good cleanup${PORT:-3000} should have been the pattern all along; this PR makes it consistent across OSS and Pro fixtures.
  • configure_ports uses ||= correctly — won't stomp on env vars a user already set intentionally.

Issues

Bug (medium) — derived ports can overflow 65535.
base_port validates the raw env var is in 1..65535 but doesn't check that base + 2 (the renderer offset) also stays in range. REACT_ON_RAILS_BASE_PORT=65534 would produce a renderer port of 65536, which is invalid. The fix is to cap the valid base range at 65535 - BASE_PORT_RENDERER_OFFSET. See inline comment.

Misleading log when PORT is already in the environment.
select_ports prints "Base port 4000 detected. Using Rails :4000, webpack :4001, renderer :4002" unconditionally when a base port is found, but configure_ports uses ||= — so a pre-existing PORT=3000 would still be honoured and Rails would actually run on 3000. The log then contradicts reality. Harmless in the primary coding-agent use case (where PORT wouldn't be pre-set), but worth tightening. See inline comment.

Minor — DEFAULT_RENDERER_PORT constant is unused.
It's defined at the top of PortSelector but never referenced in code. The Procfiles and generator both hardcode 3800 inline. Either use the constant or remove it. See inline comment.

Nice-to-have — REACT_RENDERER_URL not auto-derived from explicit RENDERER_PORT.
Manual-worktree users who set RENDERER_PORT=3801 also have to remember to set REACT_RENDERER_URL=http://localhost:3801. A one-liner in configure_ports could derive it automatically. The docs cover it, but easy to miss. See inline comment (can defer to follow-up).

Test Coverage Gap

There's no test for the scenario where REACT_ON_RAILS_BASE_PORT is set and PORT is already in the environment. Given the misleading-log issue above, a test that asserts configure_ports honours the pre-existing PORT (or alternatively overrides it — whichever the intended behavior is) would prevent regressions once the behavior is clarified.

Docs

The new process-managers.md section and .env.example comments are clear and accurate. The offset table is a nice touch. One small nit: the "Priority chain" callout in the docs says "base port > explicit per-service env vars" — worth noting that this is the select_ports priority, not the final configure_ports priority (which still lets pre-existing env vars win via ||=).

Summary

The approach is sound and the feature is useful. The port-overflow bug is the one concrete correctness issue to fix before merging. The log/priority conflict is worth at least a decision: either document that base port always wins (and change ||= to = in configure_ports) or document that pre-existing env vars take final precedence (and fix the log). Everything else can be a follow-up.

🤖 Generated with Claude Code

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37a9ae17d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 15, 2026

Greptile Summary

Adds REACT_ON_RAILS_BASE_PORT (with CONDUCTOR_PORT as a fallback) to derive all dev service ports from a single base value, enabling concurrent worktree usage without manual per-service config. Procfiles are updated to shell-default patterns (${PORT:-3000}), renderer env vars are propagated through the process manager, and docs/.env.example are expanded with coding-agent guidance.

Prior review concerns — derived renderer port exceeding 65535 and test-environment isolation — have been addressed. Two minor points remain:

  • base_port warns on non-integer values but silently ignores out-of-range integers; a consistent warning would help diagnose near-max typos.
  • sync_renderer_port_and_url uses String#include? for the port-in-URL check, which can miss a mismatch when the renderer port number is a numeric prefix of the URL's actual port (e.g. 3800 vs. :38000).

Confidence Score: 5/5

Safe to merge — no blocking issues; remaining findings are minor UX/edge-case P2s.

Previous P0/P1 concerns (renderer port > 65535, test env isolation) have been fully addressed. The two remaining issues are P2: a missing warning for out-of-range base ports (silent fallback is safe) and a false-negative in the port-prefix substring check (only affects the warning, not actual behavior). Test coverage is thorough and the core port-selection logic is correct.

react_on_rails/lib/react_on_rails/dev/port_selector.rb and react_on_rails/lib/react_on_rails/dev/server_manager.rb have the minor findings noted above.

Important Files Changed

Filename Overview
react_on_rails/lib/react_on_rails/dev/port_selector.rb New base-port logic: validates integer format (warns on bad value) but silently ignores out-of-range integers without a warning; otherwise well-structured with correct MAX_BASE_PORT guard.
react_on_rails/lib/react_on_rails/dev/server_manager.rb Port configuration split into base-port (unconditional override) and explicit paths; sync_renderer_port_and_url mismatch check uses substring matching which can produce false negatives for port-prefix collisions.
react_on_rails/lib/react_on_rails/dev/process_manager.rb Correctly extends ENV_KEYS_TO_PRESERVE with RENDERER_PORT and REACT_RENDERER_URL so the vars survive with_unbundled_env when spawning the process manager.
react_on_rails/spec/react_on_rails/dev/port_selector_spec.rb Top-level around block properly isolates all relevant env vars; comprehensive new coverage for base-port derivation, precedence, out-of-range, and overflow boundary.
react_on_rails/spec/react_on_rails/dev/server_manager_spec.rb New contexts cover base-port override, explicit RENDERER_PORT derivation, URL-port consistency warning, and internal-host URL (renderer.internal) — good coverage of the new sync logic.
react_on_rails/lib/generators/react_on_rails/pro_setup.rb Generator line updated to RENDERER_PORT=${RENDERER_PORT:-3800} — allows runtime override while preserving the previous default, consistent with Procfile changes.
docs/oss/building-features/process-managers.md Adds clear "Coding Agent / CI Integration" and "Manual Worktree Port Setup (Pro)" sections with port-offset table, priority chain, and configuration examples.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([bin/dev start]) --> B{REACT_ON_RAILS_BASE_PORT or CONDUCTOR_PORT set?}
    B -->|Yes - first valid var wins| C[base_port returns Integer]
    C --> D[select_ports: derive rails=base+0, webpack=base+1, renderer=base+2]
    D --> E[apply_base_port_env: unconditionally set PORT, SHAKAPACKER_DEV_SERVER_PORT, RENDERER_PORT, REACT_RENDERER_URL]
    B -->|No| F{PORT or SHAKAPACKER_DEV_SERVER_PORT set?}
    F -->|Yes| G[Use explicit per-service vars]
    F -->|No| H[Auto-detect free ports starting from defaults]
    G --> I[apply_explicit_port_env: PORT and WEBPACK assigned conditionally]
    H --> I
    I --> J{RENDERER_PORT set?}
    J -->|No| K([Done - renderer not configured])
    J -->|Yes| L{REACT_RENDERER_URL set?}
    L -->|No| M[Derive URL from RENDERER_PORT]
    L -->|Yes - port matches| N([Done - consistent])
    L -->|Yes - port mismatch| O[Warn: vars disagree]
    E --> P([ENV_KEYS_TO_PRESERVE propagated to spawned process manager])
    M --> P
    N --> P
    O --> P
Loading

Reviews (3): Last reviewed commit: "fix: second round of port selector revie..." | Re-trigger Greptile

Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb
Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb
Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@react_on_rails/lib/react_on_rails/dev/port_selector.rb`:
- Around line 98-102: The base_port method currently allows ENV values up to
65_535 but then derives additional ports as base+1 and base+2; update the
validation in base_port (and any use of BASE_PORT_ENV_VARS) to ensure the chosen
base plus the highest offset (2) stays ≤ 65_535 (i.e., accept bases in the range
1..65_533), so change the conditional that returns val to check val between 1
and (65_535 - 2) and reject/skip values outside that safe range.

In `@react_on_rails/lib/react_on_rails/dev/server_manager.rb`:
- Around line 842-845: The current code may set ENV["REACT_RENDERER_URL"] using
selected[:renderer] even when an existing ENV["RENDERER_PORT"] was preserved;
update the logic so REACT_RENDERER_URL is constructed from the effective port
value (the ENV["RENDERER_PORT"] after the ||= assignment) rather than from
selected[:renderer]; locate the block that references selected[:renderer] in
server_manager.rb (the if selected[:renderer] branch) and change the URL
construction to use ENV["RENDERER_PORT"] (or a local port variable assigned from
ENV["RENDERER_PORT"]) so the URL always matches the actual RENDERER_PORT in use.

In `@react_on_rails/spec/react_on_rails/dev/port_selector_spec.rb`:
- Around line 10-15: Wrap each around hook's example.run in a begin...ensure so
ENV cleanup always runs (e.g., change the around do |example| block that sets
ENV["REACT_ON_RAILS_BASE_PORT"] to use begin; example.run; ensure ... end), and
in the "CONDUCTOR_PORT only" and "out-of-range base port" contexts explicitly
unset or restore both ENV["REACT_ON_RAILS_BASE_PORT"] and ENV["CONDUCTOR_PORT"]
(capture old values, set the test values, run example, then in ensure restore
originals or delete the keys when nil) so base_port's lookup order cannot be
influenced by ambient env vars.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e7ac5b86-7952-413d-89b3-278d22119277

📥 Commits

Reviewing files that changed from the base of the PR and between de5b53a and 37a9ae1.

📒 Files selected for processing (17)
  • docs/oss/building-features/process-managers.md
  • react_on_rails/lib/generators/react_on_rails/pro_setup.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/.env.example
  • react_on_rails/lib/react_on_rails/dev/port_selector.rb
  • react_on_rails/lib/react_on_rails/dev/server_manager.rb
  • react_on_rails/spec/dummy/Procfile.dev
  • react_on_rails/spec/dummy/Procfile.dev-prod-assets
  • react_on_rails/spec/dummy/Procfile.dev-static-assets
  • react_on_rails/spec/dummy/Procfile.dev.no.turbolinks
  • react_on_rails/spec/react_on_rails/dev/port_selector_spec.rb
  • react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
  • react_on_rails/spec/react_on_rails/support/shared_examples/pro_generator_examples.rb
  • react_on_rails_pro/spec/dummy/Procfile.dev
  • react_on_rails_pro/spec/dummy/Procfile.prod
  • react_on_rails_pro/spec/dummy/Procfile.static
  • react_on_rails_pro/spec/execjs-compatible-dummy/Procfile.dev
  • react_on_rails_pro/spec/execjs-compatible-dummy/Procfile.dev-static

Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Comment thread react_on_rails/spec/react_on_rails/dev/port_selector_spec.rb
ihabadham added a commit that referenced this pull request Apr 15, 2026
## Summary

- Applies `prettier --write` to five tracked docs files that are
currently out of compliance on `main`.
- No content changes — only whitespace/table-alignment fixes produced by
Prettier.

## Why this is needed

The formatting drift landed via a docs-only merge to `main`. The
`lint-js-and-ruby.yml` workflow is conditionally skipped for docs-only
commits on `main` (see the job condition at
[`.github/workflows/lint-js-and-ruby.yml:79-87`](https://github.com/shakacode/react_on_rails/blob/main/.github/workflows/lint-js-and-ruby.yml#L79-L87)),
so the check never ran on merge. It surfaces on every subsequent
code-touching PR because the lint job runs for those (e.g.
[#3151](#3151),
[#3148](#3148),
[#3142](#3142),
[#3097](#3097)).

Merging this unblocks the `build` → `Check formatting` step on those
PRs.

## Test plan

- [x] `pnpm start format.listDifferent` passes for the five files
- [x] Diff is purely whitespace — no link targets, code blocks, or prose
changed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* **Documentation**
* Improved formatting across API reference, core concepts, migration,
and RSC docs: refined table layouts, adjusted column alignment, and
standardized emphasis for clearer warnings.
* Updated link text and phrasing for several guides for clearer
navigation.
* Reformatted code examples and JSX/Route snippets for more compact,
consistent presentation and improved readability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
@justin808 justin808 force-pushed the jg/concurrent-worktree-ports branch from 4216467 to 8da5a8a Compare April 18, 2026 01:59
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 18, 2026

Code Review

Overall this is a clean, well-motivated feature. The implementation is straightforward and the test coverage is solid. I found a few issues worth addressing before merge.

Bug: base-port priority not enforced in configure_ports

The documented priority chain states "base port > explicit per-service env vars", but the implementation doesn't honour this. In server_manager.rb:

ENV["PORT"] ||= selected[:rails].to_s

If PORT is already set in the environment (e.g. by a CI system or a shell export), ||= is a no-op. select_ports still returns the base-derived port in selected[:rails], but the Procfile reads ${PORT:-3000} — so Rails actually starts on the pre-existing PORT value, silently disagreeing with the log line that says "Using Rails :<base+0>". Either:

  • Override explicitly when a base port is detected: ENV["PORT"] = selected[:rails].to_s (only in the base-port branch), or
  • Downgrade the documented priority to say base port only wins when PORT is not already set.

Bug: derived ports are not range-validated

base_port validates that the base itself is in 1..65_535, but the derived ports are not checked. A base port of 65_534 would produce renderer port 65_536, which is invalid and would cause a confusing runtime error rather than a clear message.

Dead code: DEFAULT_RENDERER_PORT = 3800

This constant is defined but never referenced anywhere in the Ruby source — the renderer default of 3800 lives only in the Procfile shell fallback (${RENDERER_PORT:-3800}). Either use the constant in code or remove it to avoid misleading future readers.

Missing test: base port takes priority over pre-set PORT

There is no test covering the interaction where REACT_ON_RAILS_BASE_PORT is set AND PORT is already set. This gap would have caught the ||= priority issue above.

Minor: CONDUCTOR_PORT assumption needs verification

The PR assumes CONDUCTOR_PORT is the workspace's allocated base port. If Conductor.build uses that variable for its own internal API rather than the app's port, silently treating it as a Rails base port could cause surprising conflicts. Worth a note in the docs or a code comment citing the Conductor.build docs page that confirms this.


None of these are blockers for the feature concept, but the ||= priority bug and the overflow bug could cause hard-to-diagnose issues in the exact concurrent-workspace scenario this PR is designed to solve.

Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Comment thread react_on_rails/spec/react_on_rails/dev/port_selector_spec.rb
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8da5a8a004

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
react_on_rails/lib/react_on_rails/dev/server_manager.rb (1)

836-843: ⚠️ Potential issue | 🟠 Major

Apply selected ports as the effective env values.

PortSelector now gives the base port higher priority, but ||= lets pre-existing PORT / SHAKAPACKER_DEV_SERVER_PORT / RENDERER_PORT override that selection before child processes start. This can make REACT_ON_RAILS_BASE_PORT=4000 still boot on stale per-service ports, and it also preserves the previously flagged renderer URL drift.

🐛 Proposed fix
 def configure_ports
   selected = PortSelector.select_ports
-  ENV["PORT"] ||= selected[:rails].to_s
-  ENV["SHAKAPACKER_DEV_SERVER_PORT"] ||= selected[:webpack].to_s
+  ENV["PORT"] = selected[:rails].to_s
+  ENV["SHAKAPACKER_DEV_SERVER_PORT"] = selected[:webpack].to_s
   if selected[:renderer]
-    ENV["RENDERER_PORT"] ||= selected[:renderer].to_s
-    ENV["REACT_RENDERER_URL"] ||= "http://localhost:#{selected[:renderer]}"
+    ENV["RENDERER_PORT"] = selected[:renderer].to_s
+    ENV["REACT_RENDERER_URL"] ||= "http://localhost:#{ENV['RENDERER_PORT']}"
   end
 rescue PortSelector::NoPortAvailable => e
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/lib/react_on_rails/dev/server_manager.rb` around lines 836 -
843, The configure_ports method currently uses ||= which lets pre-existing ENV
values override PortSelector.select_ports; change the assignments in
configure_ports (the selected = PortSelector.select_ports block) to always set
ENV["PORT"], ENV["SHAKAPACKER_DEV_SERVER_PORT"], and when selected[:renderer]
set ENV["RENDERER_PORT"] and ENV["REACT_RENDERER_URL"] using direct assignment
(=) so the selected ports become the effective env values for child processes
and the renderer URL is updated consistently.
react_on_rails/lib/react_on_rails/dev/port_selector.rb (1)

98-102: ⚠️ Potential issue | 🟠 Major

Validate the base port against the highest derived offset.

Line 101 accepts 65_534 and 65_535, but select_ports derives webpack = base + 1 and renderer = base + 2, producing invalid ports above 65_535.

🐛 Proposed fix
 def base_port
+  max_base_port = 65_535 - BASE_PORT_RENDERER_OFFSET
   BASE_PORT_ENV_VARS.each do |var|
     val = ENV[var]&.to_i
-    return val if val&.between?(1, 65_535)
+    return val if val&.between?(1, max_base_port)
   end
   nil
 end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/lib/react_on_rails/dev/port_selector.rb` around lines 98 -
102, The base_port method currently accepts ENV values up to 65_535 but
select_ports computes webpack = base + 1 and renderer = base + 2, which can
yield ports > 65_535; update the validation in base_port (method name base_port,
referenced by select_ports) to enforce an upper bound of 65_533 (i.e., ensure
val.between?(1, 65_533) or subtract the maximum derived offset before
validation) so that adding +1 and +2 cannot produce invalid ports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@react_on_rails/lib/react_on_rails/dev/port_selector.rb`:
- Around line 98-102: The base_port method currently accepts ENV values up to
65_535 but select_ports computes webpack = base + 1 and renderer = base + 2,
which can yield ports > 65_535; update the validation in base_port (method name
base_port, referenced by select_ports) to enforce an upper bound of 65_533
(i.e., ensure val.between?(1, 65_533) or subtract the maximum derived offset
before validation) so that adding +1 and +2 cannot produce invalid ports.

In `@react_on_rails/lib/react_on_rails/dev/server_manager.rb`:
- Around line 836-843: The configure_ports method currently uses ||= which lets
pre-existing ENV values override PortSelector.select_ports; change the
assignments in configure_ports (the selected = PortSelector.select_ports block)
to always set ENV["PORT"], ENV["SHAKAPACKER_DEV_SERVER_PORT"], and when
selected[:renderer] set ENV["RENDERER_PORT"] and ENV["REACT_RENDERER_URL"] using
direct assignment (=) so the selected ports become the effective env values for
child processes and the renderer URL is updated consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 89dd322b-8ac2-4e3f-9f75-b9ef2476038a

📥 Commits

Reviewing files that changed from the base of the PR and between 4216467 and 8da5a8a.

📒 Files selected for processing (18)
  • .gitignore
  • docs/oss/building-features/process-managers.md
  • react_on_rails/lib/generators/react_on_rails/pro_setup.rb
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/.env.example
  • react_on_rails/lib/react_on_rails/dev/port_selector.rb
  • react_on_rails/lib/react_on_rails/dev/server_manager.rb
  • react_on_rails/spec/dummy/Procfile.dev
  • react_on_rails/spec/dummy/Procfile.dev-prod-assets
  • react_on_rails/spec/dummy/Procfile.dev-static-assets
  • react_on_rails/spec/dummy/Procfile.dev.no.turbolinks
  • react_on_rails/spec/react_on_rails/dev/port_selector_spec.rb
  • react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
  • react_on_rails/spec/react_on_rails/support/shared_examples/pro_generator_examples.rb
  • react_on_rails_pro/spec/dummy/Procfile.dev
  • react_on_rails_pro/spec/dummy/Procfile.prod
  • react_on_rails_pro/spec/dummy/Procfile.static
  • react_on_rails_pro/spec/execjs-compatible-dummy/Procfile.dev
  • react_on_rails_pro/spec/execjs-compatible-dummy/Procfile.dev-static
✅ Files skipped from review due to trivial changes (13)
  • .gitignore
  • react_on_rails_pro/spec/execjs-compatible-dummy/Procfile.dev-static
  • react_on_rails/spec/dummy/Procfile.dev-static-assets
  • react_on_rails/spec/dummy/Procfile.dev.no.turbolinks
  • react_on_rails/spec/dummy/Procfile.dev-prod-assets
  • react_on_rails_pro/spec/execjs-compatible-dummy/Procfile.dev
  • react_on_rails/lib/generators/react_on_rails/pro_setup.rb
  • react_on_rails_pro/spec/dummy/Procfile.prod
  • react_on_rails/lib/generators/react_on_rails/templates/base/base/.env.example
  • react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
  • react_on_rails/spec/react_on_rails/support/shared_examples/pro_generator_examples.rb
  • react_on_rails/spec/dummy/Procfile.dev
  • docs/oss/building-features/process-managers.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • react_on_rails_pro/spec/dummy/Procfile.static
  • react_on_rails_pro/spec/dummy/Procfile.dev
  • react_on_rails/spec/react_on_rails/dev/port_selector_spec.rb

justin808 added a commit that referenced this pull request Apr 18, 2026
Addresses review comments on PR #3142 (REACT_ON_RAILS_BASE_PORT):

- Validate base port against the largest derived offset so base+N stays
  within the valid TCP port range (1..65_535). A base of 65_534 previously
  passed validation but produced an invalid renderer port of 65_536.
- Enforce the documented priority chain ("base port > explicit per-service
  env vars") in ServerManager.configure_ports. The former `||=` assignments
  silently preserved a pre-existing PORT/SHAKAPACKER_DEV_SERVER_PORT, making
  the effective Rails port disagree with the "Base port detected..." log line.
  When a base port is active, per-service env vars are now assigned
  unconditionally.
- Preserve RENDERER_PORT and REACT_RENDERER_URL across Bundler.with_unbundled_env
  by adding them to ProcessManager::ENV_KEYS_TO_PRESERVE. Previously they
  were dropped for process managers launched outside the bundler context.
- Derive REACT_RENDERER_URL from an explicit RENDERER_PORT in non-base-port
  worktrees when the user set the port but not the URL.
- Remove the unused DEFAULT_RENDERER_PORT constant (the 3800 fallback only
  lives in Procfile shell expressions).
- Add port_selector_spec cases for the max-safe and overflow base ports,
  and server_manager_spec cases for base-port priority override and
  explicit-RENDERER_PORT URL sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@justin808
Copy link
Copy Markdown
Member Author

Review Feedback Addressed (commit b6e16df)

I (Claude Code) went through all inline review comments from @claude, @cursor, @chatgpt-codex-connector, @coderabbitai, and @greptile and addressed every real issue raised. After triage (deduplicating overlapping comments across reviewers), six distinct concerns were identified and all six are now fixed in a single commit.

Fixes

  1. Base port overflow (P1/Major from multiple reviewers)PortSelector.base_port now validates against MAX_BASE_PORT (65_535 - BASE_PORT_RENDERER_OFFSET). A base of 65_534 previously passed the 1..65_535 check but produced an invalid renderer port of 65_536.
  2. Base port priority violated by ||= (cursor High, claude)ServerManager.configure_ports now splits the base-port and explicit-port paths. When a base port is active, PORT / SHAKAPACKER_DEV_SERVER_PORT / RENDERER_PORT / REACT_RENDERER_URL are assigned unconditionally so the effective ports match the Base port … detected log line even when those env vars were pre-set (e.g., from a .env file).
  3. RENDERER_PORT / REACT_RENDERER_URL lost across Bundler.with_unbundled_env (codex P1) — Added both keys to ProcessManager::ENV_KEYS_TO_PRESERVE so they survive run_process_outside_bundle.
  4. Missing test for configure_ports when PORT is pre-set with base port active (claude) — Added a when configuring ports with a base port active context to server_manager_spec that covers the override.
  5. Unused DEFAULT_RENDERER_PORT constant (claude ×2, cursor Low) — Removed. The 3800 fallback only lives in the Procfile shell expression ${RENDERER_PORT:-3800}.
  6. REACT_RENDERER_URL can drift from RENDERER_PORT (coderabbit Major, claude) — In the non-base-port path, configure_ports now derives REACT_RENDERER_URL from an explicit RENDERER_PORT when only the port is set. Added a regression spec.

Tests

Added 5 new specs to port_selector_spec.rb and server_manager_spec.rb covering the max-safe base port, overflow fallback, base-port override of pre-existing env vars, and explicit-RENDERER_PORT URL derivation. Full suite (176 examples) passes locally.

On the bot summary comments (@claude, @greptile, @coderabbitai)

Their summary bodies restate the inline findings that are already addressed above — no separate action needed. The earlier CodeRabbit "processing" notice is a stale status post.

All 16 unresolved review threads are now resolved.

Comment thread react_on_rails/spec/react_on_rails/dev/server_manager_spec.rb Fixed
Comment thread react_on_rails/spec/react_on_rails/dev/server_manager_spec.rb Fixed
@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@coderabbitai coderabbitai Bot added the full-ci label Apr 18, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me quickly verify the key changes in the updated code.


🏁 Script executed:

#!/bin/bash
echo "=== PortSelector: base_port method + MAX_BASE_PORT ==="
rg -n "MAX_BASE_PORT|base_port|BASE_PORT_RENDERER_OFFSET" react_on_rails/lib/react_on_rails/dev/port_selector.rb -A2 -B2

echo ""
echo "=== ServerManager: configure_ports ==="
cat react_on_rails/lib/react_on_rails/dev/server_manager.rb

echo ""
echo "=== ProcessManager: ENV_KEYS_TO_PRESERVE ==="
rg -n "ENV_KEYS_TO_PRESERVE" -A5 -B2

echo ""
echo "=== DEFAULT_RENDERER_PORT removed? ==="
rg -n "DEFAULT_RENDERER_PORT" react_on_rails/lib/react_on_rails/dev/port_selector.rb

Length of output: 58432


@justin808 — Thank you for the thorough write-up! I've verified all six fixes in the updated code:

  1. MAX_BASE_PORT overflow guard ✅ — base_port now validates against MAX_BASE_PORT = 65_535 - BASE_PORT_RENDERER_OFFSET, correctly rejecting values like 65,534 that would produce an out-of-range renderer port.

  2. Unconditional base-port assignment ✅ — configure_ports is cleanly split into apply_base_port_env (assigns all four env vars unconditionally) and apply_explicit_port_env (uses ||=), so the "Base port detected" log line always matches the effective ports.

  3. ENV_KEYS_TO_PRESERVE ✅ — Both RENDERER_PORT and REACT_RENDERER_URL are present in ProcessManager::ENV_KEYS_TO_PRESERVE, ensuring they survive Bundler.with_unbundled_env.

  4. DEFAULT_RENDERER_PORT removed ✅ — No matches found; the 3800 fallback now lives exclusively in the Procfile shell expression.

  5. REACT_RENDERER_URL/RENDERER_PORT drift (my Major comment) ✅ — apply_explicit_port_env now derives REACT_RENDERER_URL from an explicit RENDERER_PORT when the URL is unset.

Everything looks solid. The apply_base_port_env / apply_explicit_port_env split is a nice clean separation of concerns too. Great work resolving all of the feedback!


🧠 Learnings used
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 0
File: :0-0
Timestamp: 2026-04-13T02:29:20.920Z
Learning: In `react_on_rails_pro/lib/react_on_rails_pro/engine.rb` and `react_on_rails/lib/react_on_rails/configuration.rb` (PR `#3094`): Do NOT reset `rendering_strategy` or `js_code_builder` inside `ReactOnRails.configure`. The Pro engine sets these exactly once during `after_initialize`; resetting them on reconfigure would silently drop Pro's NodeStrategy/ProJsCodeBuilder assignments. A proper reconfigure mechanism for these accessors is deferred to Step 3 of the strategy-pattern refactor (`#2905`).

Learnt from: ihabadham
Repo: shakacode/react_on_rails PR: 2284
File: react_on_rails/lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt:10-10
Timestamp: 2026-02-12T20:43:26.843Z
Learning: In React on Rails Pro initializer templates (e.g., `react_on_rails_pro.rb.tt`), the `config.renderer_password` uses `ENV.fetch("RENDERER_PASSWORD", "devPassword")` as a development starter configuration. This pattern is intentional: the node renderer requires explicit production deployment setup, and adding a production-only raise would break generator execution since initializers are loaded during `rails generate`. The pattern is consistent with existing manual setup documentation.

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/spec/dummy/commonWebpackConfig.js : Changes to `commonWebpackConfig.js` affect all three webpack bundles (client, server, and RSC) in the Pro dummy app

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-12T23:15:11.806Z
Learning: Applies to CHANGELOG.md : Changelog format: `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)` (no hash before PR number)

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/**/*.{js,ts,tsx,jsx,json,css,md} : Pro Prettier check should be run with: `cd react_on_rails_pro && pnpm run prettier --check .`

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/spec/react_on_rails_pro/**/*.rb : Pro gem unit tests should be run with: `cd react_on_rails_pro && bundle exec rspec spec/react_on_rails_pro/`

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/spec/dummy/spec/**/*.rb : Pro integration tests should be run with: `cd react_on_rails_pro/spec/dummy && bundle exec rspec spec/`

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-17T22:10:33.821Z
Learning: Applies to react_on_rails_pro/** : Ask before modifying the Pro package (`react_on_rails_pro/`)

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/packages/react-on-rails-pro/**/*.test.rsc.{ts,tsx} : Pro RSC tests should be run with: `pnpm --filter react-on-rails-pro run test:rsc`

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-12T23:15:11.806Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.{js,ts} : Use Playwright for E2E tests in `react_on_rails/spec/dummy/e2e/playwright/e2e/`

Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb
Comment thread react_on_rails/lib/react_on_rails/dev/file_manager.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/file_manager.rb
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53c62ee026

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/file_manager.rb
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d328f8b4b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb Outdated
Comment thread react_on_rails_pro/spec/dummy/Procfile.static Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ef6d50242

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb
Comment thread react_on_rails/lib/react_on_rails/dev/port_selector.rb
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 9 total unresolved issues (including 8 from previous reviews).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Kill command targets wrong port from localhost renderer URL
    • The kill-port fallback now extracts an explicit port from localhost renderer URLs before falling back to the default renderer port.

Create PR

Or push these changes by commenting:

@cursor push 5487ee342c
Preview (5487ee342c)
diff --git a/react_on_rails/lib/react_on_rails/dev/server_manager.rb b/react_on_rails/lib/react_on_rails/dev/server_manager.rb
--- a/react_on_rails/lib/react_on_rails/dev/server_manager.rb
+++ b/react_on_rails/lib/react_on_rails/dev/server_manager.rb
@@ -87,11 +87,29 @@
         def configured_renderer_port_for_kill
           raw_port = ENV.fetch("RENDERER_PORT", nil)
           return raw_port.strip.to_i if valid_port_string?(raw_port)
+
+          local_renderer_url_port = configured_local_renderer_url_port_for_kill
+          return local_renderer_url_port if local_renderer_url_port
+
           return nil if remote_renderer_url_configured?
 
           3800
         end
 
+        def configured_local_renderer_url_port_for_kill
+          %w[REACT_RENDERER_URL RENDERER_URL].each do |var|
+            url = ENV.fetch(var, nil)
+            next if url.nil? || url.strip.empty? || !localhost_renderer_url?(url)
+            next unless url.match?(URL_WITH_EXPLICIT_PORT_RE)
+
+            return URI.parse(url).port
+          rescue URI::InvalidURIError
+            next
+          end
+
+          nil
+        end
+
         def remote_renderer_url_configured?
           %w[REACT_RENDERER_URL RENDERER_URL].any? do |var|
             url = ENV.fetch(var, nil)

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit db313f1. Configure here.

Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: db313f17d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 4, 2026


Code Review: PR #3142

Overview

Well-structured PR solving a real pain point (port conflicts across concurrent worktrees/agent sandboxes) with a clean priority-chain design. Test coverage is thorough and inline documentation is unusually good. One clear bug found where a test documents behavior that the implementation does not actually provide.


Bug: configured_renderer_port_for_kill does not extract port from a localhost REACT_RENDERER_URL

The newly added test "targets the local renderer URL port when RENDERER_PORT is not set" expects that when only REACT_RENDERER_URL=http://localhost:3900 is set (no RENDERER_PORT), kill_processes targets port 3900. The implementation does not do this.

Trace through configured_renderer_port_for_kill:

  1. RENDERER_PORT is unset → valid_port_string? returns false → skip
  2. remote_renderer_url_configured? returns false because http://localhost:3900 IS a localhost URL
  3. Falls through to the hardcoded 3800 default
  4. killable_ports builds [3000, 3001, 3800] — port 3900 is never scanned

The test asserts both expect(Open3).not_to receive(:capture2).with("lsof", "-ti", ":3800", ...) and expect(Process).to receive(:kill).with("TERM", 3901), both of which would fail.

Suggested fix — add a URL port extraction step before the fallback:

def configured_renderer_port_for_kill
  raw_port = ENV.fetch("RENDERER_PORT", nil)
  return raw_port.strip.to_i if valid_port_string?(raw_port)
  return nil if remote_renderer_url_configured?

  local_url = ENV.fetch("REACT_RENDERER_URL", nil) || ENV.fetch("RENDERER_URL", nil)
  if local_url && !local_url.strip.empty? && localhost_renderer_url?(local_url)
    port = URI.parse(local_url).port rescue nil
    return port if port&.between?(1, 65_535)
  end

  3800
end

Minor: ENV mutation in select_ports without a ! suffix

PortSelector.select_ports deletes invalid PORT/SHAKAPACKER_DEV_SERVER_PORT values from ENV via read_and_sanitize_port_env!. The docstring calls this out clearly, but a public class method mutating a global singleton without a ! suffix is still a surprise. Low severity for a dev-only tool.


Minor: SHAKAPACKER_DEV_SERVER_PORT set in prod mode

apply_base_port_env sets SHAKAPACKER_DEV_SERVER_PORT even in bin/dev prod, which does not run webpack-dev-server. The comment explains this is intentional for tooling consistency and tests lock it in. Valid design choice, but could confuse developers who inspect env vars after bin/dev prod.


What is done well

  • Test depth: edge cases (IPv6 URLs, basic-auth passwords with digit substrings, whitespace-padded env vars, port-as-substring) reflect production incident thinking.
  • No shell injection: all lsof/pgrep/Open3 calls use word-list argv throughout.
  • socket_active? with bounded timeout: connect_nonblock + SO_ERROR probe avoids stalling on a stuck Overmind socket; wait_writable is correct for Fiber scheduler compatibility.
  • Proc/lsof dual-probe in working_directory_for_pid: fallback from /proc/PID/cwd to lsof covers both Alpine containers (no lsof) and macOS (no /proc).
  • URL_WITH_EXPLICIT_PORT_RE regex: userinfo prefix guard and IPv6 literal alternative correctly handle the basic-auth and IPv6 edge cases called out in the tests.
  • valid_port_string? as shared predicate: keeps ServerManager and PortSelector from silently diverging on port string validation.
  • base_port_hash vs base_port_ports: clean separation between the silent kill path and the startup path that logs the detected base port.
  • CONDUCTOR_PORT caveat: best-effort disclaimer placed in both code comments and the docs.

CI is unchecked. The failing test will surface the bug automatically once CI runs.

Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 4, 2026

Code Review: PR #3142REACT_ON_RAILS_BASE_PORT for concurrent worktree port management

Overview

Clean, well-scoped feature. The design decisions are sound — the base_port_ports (verbose) vs base_port_hash (silent) split cleanly separates startup logging from kill-flow queries, ENV mutation is clearly documented with ! conventions, and the hedge on CONDUCTOR_PORT's undocumented contract is the right call.

Test coverage is excellent: 300+ new lines covering base-port derivation, priority chain, privileged ports, overflow, whitespace, IPv6, basic-auth URLs, and kill-process targeting. The with clean port env shared context protecting specs from shell leakage is a nice touch.

Issues

1. warn message missing WARNING: prefix (inconsistency)

In sync_renderer_port_and_url (server_manager.rb:1124), the derivation message is the only ENV-mutation warn line in the file without a WARNING: prefix:

warn "RENDERER_PORT=#{port} set without REACT_RENDERER_URL; deriving REACT_RENDERER_URL=#{derived}."

Every other ENV-mutation message starts with WARNING:. This breaks grep WARNING bin/dev.log filtering and is surprising for operators scanning output. See inline comment.

2. Whitespace-padded PORT / SHAKAPACKER_DEV_SERVER_PORT not normalized in ENV (explicit-port path)

valid_port_string? accepts " 3000 " as valid, so overwrite_invalid_port_env returns early and leaves the whitespace-padded value in ENV. The Procfile then receives ${PORT:-3000}3000 (with spaces), passed to rails s -p 3000 . Most shells handle this silently, but it's inconsistent with RENDERER_PORT, which is stripped before being written back:

# sync_renderer_port_and_url — RENDERER_PORT IS normalized
ENV["RENDERER_PORT"] = port if port != raw_port

Fix: add ENV[var_name] = existing.strip when the value is valid but contains surrounding whitespace. See inline comment.

3. pro_renderer_active? activates on the legacy RENDERER_URL

%w[RENDERER_PORT REACT_RENDERER_URL RENDERER_URL].any? { ... }

A user who still has RENDERER_URL in their env gets silently pulled into the Pro renderer path — including having REACT_RENDERER_URL derived or overridden in base-port mode. This is probably correct, but the existing rename warning ("RENDERER_URL is set but REACT_RENDERER_URL is not") doesn't mention that RENDERER_URL still activates Pro renderer behavior. A one-line addition would avoid surprise. See inline comment.

4. Double URI parse in local_renderer_url_port_for_kill (minor)

localhost_renderer_url?(url) already calls URI.parse(url) internally (and returns true only on success), then URI.parse(url).port immediately re-parses the same string. Trivial, but a single parse with result reuse would be cleaner. See inline comment.

Positive highlights

  • Non-blocking socket probe with SOCKET_PROBE_TIMEOUT_SECS cap is well-implemented — prevents a hung Overmind socket from stalling startup indefinitely.
  • /proclsof fallback for cross-directory PID checks handles both minimal Linux CI containers (no lsof) and macOS correctly.
  • ENV_KEYS_TO_PRESERVE nil-values fix in preserve_runtime_env_vars closes the Bundler resurrection bug cleanly.
  • CONDUCTOR_PORT contract hedging is appropriately documented without over-committing to an undocumented third-party API.
  • URL_WITH_EXPLICIT_PORT_RE regex comments explaining the userinfo/IPv6 subtleties are genuinely useful for future maintainers.

Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb
Comment thread react_on_rails/lib/react_on_rails/dev/server_manager.rb Outdated
Two genuine bugs and 14 polish items from the latest bot reviews.

Bugs:
- run_production_like rescues PortSelector::NoPortAvailable so an
  exhausted port range exits cleanly instead of producing an unhandled
  Ruby backtrace, mirroring configure_ports.
- warn_if_derived_ports_in_use accepts a pro_renderer keyword so OSS
  apps without the Pro renderer don't see a confusing
  "port base+2 (renderer) is already in use" warning when an unrelated
  service binds the renderer port.

Polish:
- Strip-and-write-back whitespace-padded but-otherwise-valid PORT /
  SHAKAPACKER_DEV_SERVER_PORT to match RENDERER_PORT normalization.
- Prefix the RENDERER_PORT-without-URL derivation message with WARNING:
  so grep WARNING filtering catches it.
- valid_port_string? uses the existing TCP_PORT_MAX constant.
- sync_renderer_port_and_url calls warn_url_without_port without
  pretending its return value is meaningful.
- file_manager.rb: SOCKET_PROBE_TIMEOUT_SECS hoisted to class body and
  reduced from 1.0s to 0.15s; dead File.readlink nil/empty guard
  removed.
- cleanup_socket_files (kill flow) now uses the same overmind*.sock
  glob as FileManager#cleanup_overmind_sockets so renamed/copied
  variants are removed.
- local_renderer_url_port_for_kill caches the parsed URI so
  localhost_renderer_url? doesn't re-parse the same string. Helper
  factored out as localhost_hostname?.
- Procfile.static delegates to `pnpm run node-renderer` so future
  package.json flag additions propagate without divergence.
- Docs: PortSelector#select_ports ENV mutation note strengthened with
  YARD-style @side_effect; killable_ports comments why
  pro_renderer_active? rarely fires during kill; warn_if_legacy
  message notes RENDERER_URL still activates the Pro path; CONDUCTOR_PORT
  caveat extended with the future-semantics-change failure mode.

Specs:
- New: PortSelector skips renderer port-in-use warning when
  pro_renderer is false.
- New: run_production_like exits cleanly on NoPortAvailable.
- New: kill flow cleans up renamed/copied overmind sockets via the
  shared glob.
- Existing cleanup_socket_files spec updated to stub the new glob.

All bots' "addressed" items from PR review summaries are covered or
verified already-addressed in earlier commits (4dce031..a7d748a).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@justin808
Copy link
Copy Markdown
Member Author

Round 22 review wrap-up — 16 fixes from claude/cursor/codex bots

All new review threads opened against commit a7d748a6 have been audited and addressed in commit b1cc0bb7. 31 review threads resolved.

Bugs fixed

  • run_production_like now rescues PortSelector::NoPortAvailable — exits cleanly with a one-line warning instead of producing an unhandled Ruby backtrace, mirroring configure_ports. New regression spec at server_manager_spec.rb.
  • OSS apps no longer see a renderer port-in-use warning when base+2 collides with an unrelated service — threaded a pro_renderer: keyword through select_portsbase_port_portswarn_if_derived_ports_in_use, populated by ServerManager from pro_renderer_active?. New spec at port_selector_spec.rb.

Polish

  • Whitespace-padded but-otherwise-valid PORT / SHAKAPACKER_DEV_SERVER_PORT are now stripped and written back to ENV, matching the existing RENDERER_PORT normalization. Resolves the cross-var inconsistency the cursor and claude bots both flagged.
  • The RENDERER_PORT-without-URL derivation message now starts with WARNING: so grep WARNING bin/dev.log catches it alongside every other ENV-mutation warning.
  • PortSelector.valid_port_string? now uses the existing TCP_PORT_MAX constant instead of the literal 65_535.
  • sync_renderer_port_and_url no longer pretends warn_url_without_port returns a meaningful value (return warn_url_without_port(url)warn_url_without_port(url); return).
  • FileManager::SOCKET_PROBE_TIMEOUT_SECS hoisted to the class body and reduced from 1.0 s to 0.15 s — UNIX socket connect to a dead listener fails in microseconds, so worst-case overhead across many stale sockets is now bounded.
  • Dropped the dead path.nil? || path.empty? guard on File.readlink in working_directory_via_procFile.readlink either returns a non-empty String or raises a rescued error.
  • cleanup_socket_files (kill flow) now uses the same tmp/sockets/overmind*.sock glob as FileManager#cleanup_overmind_sockets, so renamed/copied variants like overmind-4100.sock are removed during bin/dev kill too. New spec covers a copied-app socket path.
  • local_renderer_url_port_for_kill now caches the parsed URI; refactored localhost_renderer_url? to delegate to a new localhost_hostname? helper.
  • Procfile.static delegates to pnpm run node-renderer so future package.json flag additions (--max-old-space-size, --experimental-vm-modules, etc.) propagate without manual sync.

Documentation

  • select_ports docstring carries an explicit @side_effect annotation for the ENV mutation; the inner read_and_sanitize_port_env! already uses ! to make the mutation explicit at the call site. Held off on renaming the public method to avoid breaking direct callers — happy to follow up if you'd prefer.
  • pro_renderer_active? now has an inline note explaining why the legacy RENDERER_URL is intentionally included; warn_if_legacy_renderer_url_env_used calls out that the legacy var still triggers the renderer path until the user renames.
  • killable_ports has a paragraph explaining that during bin/dev kill the previous session's RENDERER_PORT/REACT_RENDERER_URL are not exported into the new shell, so in OSS apps the renderer port is included only when the Pro gem is actually loaded.
  • BASE_PORT_ENV_VARS comment extended to call out a future CONDUCTOR_PORT semantics-change failure mode (block-base → Rails port directly) and how a future "validate derived ports are reachable on startup" path would catch it.

Already addressed earlier in this PR (resolved with rationale)

Comments r3181025355, r3180916302, r3180974669, r3180966942, r3180803438 — all addressed by 5ef6d502 + a7d748a6 (local_renderer_url_port_for_kill).
Comment r3180848346 (P1 — remote renderer URL) addressed by 5ef6d502.
Comments r3178803638, r3180729124 addressed by 53c62ee0 (pro_renderer_active? gate on killable_ports).
Comments r3180787034, r3180803444 addressed by 3d328f8b (Socket.new inside begin, ArgumentError in rescue list).
Comment r3178803549 addressed earlier (port_selector.rb:230 already uses .strip.empty?).

Verification

  • bundle exec rubocop on all changed files: 0 offenses.
  • bundle exec rspec spec/react_on_rails/dev/file_manager_spec.rb spec/react_on_rails/dev/port_selector_spec.rb spec/react_on_rails/dev/server_manager_spec.rb: 250 examples, 0 failures.

I (Claude Code) made these changes in a fresh worktree on origin/jg/concurrent-worktree-ports and pushed b1cc0bb7 as a fast-forward (no force-push).

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1cc0bb7f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# * `[^@/:]+` for a regular hostname/IPv4 whose charset excludes
# `/` and `:` so the `:\d+` port anchor lands on the authority
# separator without backtracking into the host.
URL_WITH_EXPLICIT_PORT_RE = %r{://(?:[^@/]*@)?(?:\[[^\]]+\]|[^@/:]+):\d+}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Anchor explicit-port regex to authority boundary

URL_WITH_EXPLICIT_PORT_RE can match the :<digits> portion of userinfo (e.g. http://user:3800@localhost) instead of only matching a host port, because the pattern is not constrained to the end of the authority segment. In that case local_renderer_url_port_for_kill treats a URL with no explicit host port as explicit and uses URI.parse(url).port (defaulting to 80), so bin/dev kill may target port 80 and terminate an unrelated local service; the same false-positive also affects renderer URL/port mismatch checks.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex PRs created from codex-named branches full-ci

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant