fix: preserve runtime env vars across Bundler.with_unbundled_env#2836
fix: preserve runtime env vars across Bundler.with_unbundled_env#2836
Conversation
…rocessManager When foreman/overmind is system-installed (not in Gemfile), ProcessManager runs it inside Bundler.with_unbundled_env, which restores the pre-Bundler env snapshot. This wipes PORT and SHAKAPACKER_DEV_SERVER_PORT set by PortSelector.configure_ports, causing: - Foreman injects its default PORT=5000 (overriding the selected port) - Webpack dev server ignores SHAKAPACKER_DEV_SERVER_PORT (falls back to 3035) Fix: capture these env vars before entering the unbundled block and pass them explicitly to system() as an env hash — the same pattern used by Rails' bundle_command (railties), Spring's process spawning, and this codebase's own PackGenerator. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughCapture and pass selected runtime ENV keys (PORT, SHAKAPACKER_DEV_SERVER_PORT) when launching processes outside Bundler by adding an ENV_KEYS_TO_PRESERVE constant and a helper that builds an env hash, then invoking Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR fixes a well-understood Bundler behaviour where Key changes:
Issues found:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant PS as PortSelector
participant ENV as ENV (runtime)
participant PM as ProcessManager
participant BE as Bundler.with_unbundled_env
participant FM as Foreman/Overmind
PS->>ENV: ENV["PORT"] = "3001"
PS->>ENV: ENV["SHAKAPACKER_DEV_SERVER_PORT"] = "3036"
Note over PM: preserve_runtime_env_vars()
PM->>ENV: read PORT, SHAKAPACKER_DEV_SERVER_PORT
ENV-->>PM: { "PORT" => "3001", "SHAKAPACKER_DEV_SERVER_PORT" => "3036" }
PM->>BE: with_unbundled_env { ... }
Note over BE: Restores pre-Bundler ENV snapshot<br/>(PORT / SHAKAPACKER_DEV_SERVER_PORT now nil)
BE->>FM: system(env_overrides, "foreman", *args)
Note over FM: env_overrides re-injects PORT=3001<br/>SHAKAPACKER_DEV_SERVER_PORT=3036
FM-->>PM: exit status
|
ReviewThe fix correctly addresses the root cause — Bundler.with_unbundled_env restoring the pre-Bundler ENV snapshot — and the approach of capturing vars before the block and passing them via system(env, ...) is the right pattern. A few issues to address: Broken existing test The existing test at spec line 176 will now fail. Before this change, system was called as system(process, *args). After, it is called as system(env_overrides, process, *args) where env_overrides is {} when the port vars are absent. An empty hash as the first arg is a different method signature, so the expectation .with('foreman', 'start', ...) won't match .with({}, 'foreman', 'start', ...). Constant defined on the singleton class ENV_KEYS_TO_PRESERVE is defined inside class << self, which attaches it to the singleton class, not ProcessManager itself. Referencing it as ProcessManager::ENV_KEYS_TO_PRESERVE from outside the class will raise NameError. It should be moved outside class << self, alongside VERSION_CHECK_TIMEOUT at the top of the class. Missing test coverage for the new behaviour There are no tests that verify env vars are actually forwarded when set. Worth adding tests for: when PORT is set it should appear in the env hash passed to system; when SHAKAPACKER_DEV_SERVER_PORT is set the same; and when neither is set the hash is empty. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
react_on_rails/lib/react_on_rails/dev/process_manager.rb (1)
180-184: Add a regression spec for env forwarding.The helper looks good, but this path is easy to regress. Please add a spec that proves
PORTandSHAKAPACKER_DEV_SERVER_PORTsurviverun_process_outside_bundle.🤖 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/process_manager.rb` around lines 180 - 184, Add a regression spec that sets ENV["PORT"] and ENV["SHAKAPACKER_DEV_SERVER_PORT"], calls the dev process helper that runs run_process_outside_bundle (so the code path that uses preserve_runtime_env_vars is exercised), and then asserts those two keys still exist with the same values after the call; locate the code paths by referencing preserve_runtime_env_vars and run_process_outside_bundle, mock or stub external process execution as needed so the spec only verifies environment forwarding, and ensure the test fails if those env keys are removed or changed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@react_on_rails/lib/react_on_rails/dev/process_manager.rb`:
- Around line 180-184: Add a regression spec that sets ENV["PORT"] and
ENV["SHAKAPACKER_DEV_SERVER_PORT"], calls the dev process helper that runs
run_process_outside_bundle (so the code path that uses preserve_runtime_env_vars
is exercised), and then asserts those two keys still exist with the same values
after the call; locate the code paths by referencing preserve_runtime_env_vars
and run_process_outside_bundle, mock or stub external process execution as
needed so the spec only verifies environment forwarding, and ensure the test
fails if those env keys are removed or changed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b4c8db46-c3a3-4528-b767-8ff5a1d57032
📒 Files selected for processing (1)
react_on_rails/lib/react_on_rails/dev/process_manager.rb
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
The core fix is correct and follows the established pattern (Rails railties, PackGenerator). Three issues need addressing before merge: (1) ENV_KEYS_TO_PRESERVE is inside class << self, making ProcessManager::ENV_KEYS_TO_PRESERVE raise NameError — move it outside alongside VERSION_CHECK_TIMEOUT. (2) The spec update uses a_kind_of(Hash) which does not verify the hash contents — add a test that sets ENV PORT and asserts it appears in the system() call. (3) react_on_rails/sig/react_on_rails/dev/process_manager.rbs is missing ENV_KEYS_TO_PRESERVE and preserve_runtime_env_vars declarations — steep check will flag them. See inline comments for details. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
react_on_rails/spec/react_on_rails/dev/process_manager_spec.rb (1)
173-180: Consider adding a test that verifies specific env vars are preserved.The
a_kind_of(Hash)matcher verifies the structural change but doesn't assert that the correct environment variables (PORT,SHAKAPACKER_DEV_SERVER_PORT) are actually captured and passed. Since no env vars are set up in this test,preserve_runtime_env_varsreturns an empty hash.Consider adding a test case that sets these env vars and verifies they appear in the hash:
♻️ Suggested additional test
it "preserves runtime env vars when Bundler is available" do allow(ENV).to receive(:[]).and_call_original allow(ENV).to receive(:[]).with("PORT").and_return("3001") allow(ENV).to receive(:[]).with("SHAKAPACKER_DEV_SERVER_PORT").and_return("3036") expect(described_class).to receive(:with_unbundled_context).and_yield expect_any_instance_of(Kernel).to receive(:system) .with({ "PORT" => "3001", "SHAKAPACKER_DEV_SERVER_PORT" => "3036" }, "foreman", "start", "-f", "Procfile.dev") described_class.send(:run_process_outside_bundle, "foreman", ["start", "-f", "Procfile.dev"]) end🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@react_on_rails/spec/react_on_rails/dev/process_manager_spec.rb` around lines 173 - 180, Add a new spec that sets ENV["PORT"] and ENV["SHAKAPACKER_DEV_SERVER_PORT"] (stub ENV.[] to return values) and then asserts run_process_outside_bundle calls with_unbundled_context and calls Kernel.system with a hash containing those exact keys/values; specifically mock described_class.with_unbundled_context to yield and expect_any_instance_of(Kernel).to receive(:system).with({ "PORT" => "3001", "SHAKAPACKER_DEV_SERVER_PORT" => "3036" }, "foreman", "start", "-f", "Procfile.dev") so preserve_runtime_env_vars behavior is verified when invoking described_class.send(:run_process_outside_bundle, "foreman", ["start", "-f", "Procfile.dev"]).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@react_on_rails/spec/react_on_rails/dev/process_manager_spec.rb`:
- Around line 173-180: Add a new spec that sets ENV["PORT"] and
ENV["SHAKAPACKER_DEV_SERVER_PORT"] (stub ENV.[] to return values) and then
asserts run_process_outside_bundle calls with_unbundled_context and calls
Kernel.system with a hash containing those exact keys/values; specifically mock
described_class.with_unbundled_context to yield and
expect_any_instance_of(Kernel).to receive(:system).with({ "PORT" => "3001",
"SHAKAPACKER_DEV_SERVER_PORT" => "3036" }, "foreman", "start", "-f",
"Procfile.dev") so preserve_runtime_env_vars behavior is verified when invoking
described_class.send(:run_process_outside_bundle, "foreman", ["start", "-f",
"Procfile.dev"]).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 822e878d-d317-4ca9-b692-3850ccd967dd
📒 Files selected for processing (1)
react_on_rails/spec/react_on_rails/dev/process_manager_spec.rb
…sts, update RBS Move ENV_KEYS_TO_PRESERVE outside class << self to match VERSION_CHECK_TIMEOUT placement and allow external access. Add regression specs that verify PORT and SHAKAPACKER_DEV_SERVER_PORT are forwarded in the env hash passed to system(), and that unset vars are omitted. Update RBS signature with the new constant and method declarations. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
ReviewOverall: solid fix, minimal and well-reasoned. The root cause analysis is accurate — What's right
Minor concerns
Neither of these is a blocker. The fix is correct and the tests cover the meaningful cases. |
…olve-2835 * origin/main: (21 commits) docs: fix profiling node renderer command (#2863) generators: point Pro install fallback to upgrade guide (#2868) Add RSC Flight payload optimization guide (Article 7) (#2827) Migrate from deprecated Async::Variable to Async::Promise (#2832) docs: turn pro quick start into a gateway (#2862) Fix upload-assets endpoint duplicating bundles across directories (#2768) docs: fix stale docs links and help URLs (#2850) docs: replace dead pro.reactonrails.com links (#2851) docs: refresh generator and helper URLs (#2852) Add standalone RSC upgrade guide for existing Pro apps (#2831) Raise docs version floor to 16.4.0 in install/demo guidance (#2610) Fix release script: require changelog, fix RC version computation (#2848) Bump version to 16.5.0 Bump version to 16.5.0.rc.0 Update CHANGELOG.md for 16.5.0.rc.0 (#2847) Docs: add memory leak prevention guide for Node Renderer SSR (#2845) Docs: fix RSC migration gaps found during real-world migration (#2842) Add common mistakes sections to RSC migration guides (#2826) fix: preserve runtime env vars across Bundler.with_unbundled_env (#2836) Skip Pro CI workflows for Dependabot PRs (#2825) ... # Conflicts: # CHANGELOG.md
Summary
ProcessManager#run_process_outside_bundlelosingPORTandSHAKAPACKER_DEV_SERVER_PORTenv vars when running foreman/overmind insideBundler.with_unbundled_envProblem
When foreman is system-installed (recommended by foreman docs — "Don't Bundle Foreman"),
ProcessManagerruns it insideBundler.with_unbundled_env. This restores the pre-Bundler env snapshot, wiping any env vars set afterBundler.setup— includingPORTandSHAKAPACKER_DEV_SERVER_PORTset byPortSelector.configure_ports.Result: Auto-detected ports from
PortSelectornever reach foreman's child processes:PORT=5000, overriding the selected port3035fromshakapacker.ymlThis breaks the zero-config automatic port selection added in #2539.
Root Cause
Bundler.with_unbundled_envrestores a snapshot ofENVcaptured beforeBundler.setupran. Any env var set at runtime after that point isnilinside the block. This is documented Bundler behavior and a known issue across the ecosystem.Fix
Capture the port env vars before entering the unbundled block and pass them explicitly to
system()as an env hash — using Ruby'sKernel#system(env, command, *args)signature.This is the same pattern used by:
bundle_commandin railties (8.0, 8.1)Process.spawn(env_hash, ...)BundlePruner(capturesGEM_HOME/BUNDLE_GEMFILEbeforewith_unbundled_env)PackGenerator(run_via_bundle_execpasses env hash tosystem()insidewith_unbundled_context)Test plan
Tested manually with a Rails app where foreman is system-installed (not in Gemfile):
bin/dev—PortSelectordetected conflict, selected 3001/3036🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) [email protected]
Summary by CodeRabbit
Bug Fixes
Tests