Skip to content

Commit 2306825

Browse files
justin808claude
andauthored
Fix bin/dev showing 'Process Manager Not Found' when overmind exists but exits (#2087)
## Summary Fixes a bug where `bin/dev` shows the misleading error "Process Manager Not Found" when overmind is installed but exits with a non-zero code (e.g., when a Procfile process crashes). ## Problem After the v16.2.0.beta.12 release, users reported seeing: ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ERROR: Process Manager Not Found ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ To run the React on Rails development server, you need either 'overmind' or 'foreman' installed on your system. ``` This error appeared even though overmind was installed and working. It occurred when overmind successfully started but then exited with a non-zero code (e.g., when a process in the Procfile died). ## Root Cause The `run_with_process_manager` method couldn't distinguish between: - Process not found (should show installation help) - Process found but exited with error (should just exit, process manager already showed its error) The old logic: ```ruby return if run_process_if_available("overmind", ...) return if run_process_if_available("foreman", ...) show_process_manager_installation_help # Always shown if neither returned! ``` When `system('overmind', 'start', '-f', procfile)` exited with error, it returned `false`, not `nil`, so the code continued to try foreman. If foreman wasn't installed, it showed the "not found" message incorrectly. ## Solution Changed `run_process_if_available` to return **three distinct states**: - `true`: Process found and exited successfully (exit code 0) - `false`: Process found but exited with error (non-zero exit code) - `nil`: Process not found/available Updated the error handling logic: ```ruby overmind_result = run_process_if_available("overmind", ...) return if overmind_result == true # Success! foreman_result = run_process_if_available("foreman", ...) return if foreman_result == true # Success! # If found but failed, exit silently (they showed their own errors) exit 1 if overmind_result == false || foreman_result == false # Only show "not found" if NEITHER was found show_process_manager_installation_help exit 1 ``` ## Testing - ✅ Verified overmind detection still works correctly - ✅ Tested scenario where overmind exits with error - correctly exits without showing "not found" - ✅ Tested scenario where neither is installed - correctly shows installation help - ✅ RuboCop passes - ✅ RBS signatures updated and validated - ✅ Manual testing in spec/dummy ## Impact Users will no longer see the confusing "Process Manager Not Found" error when their process manager is actually installed but a Procfile process crashes or exits with error. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <[email protected]>
1 parent 3bd2f10 commit 2306825

File tree

3 files changed

+66
-8
lines changed

3 files changed

+66
-8
lines changed

lib/react_on_rails/dev/process_manager.rb

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,20 @@ def run_with_process_manager(procfile)
3636
FileManager.cleanup_stale_files
3737

3838
# Try process managers in order of preference
39-
return if run_process_if_available("overmind", ["start", "-f", procfile])
40-
return if run_process_if_available("foreman", ["start", "-f", procfile])
39+
# run_process_if_available returns:
40+
# - true/false (exit status) if process was found and executed
41+
# - nil if process was not found
42+
overmind_result = run_process_if_available("overmind", ["start", "-f", procfile])
43+
return if overmind_result == true # Overmind ran successfully
4144

45+
foreman_result = run_process_if_available("foreman", ["start", "-f", procfile])
46+
return if foreman_result == true # Foreman ran successfully
47+
48+
# If either process was found but exited with error, don't show "not found" message
49+
# The process manager itself will have shown its own error message
50+
exit 1 if overmind_result == false || foreman_result == false
51+
52+
# Neither process manager was found
4253
show_process_manager_installation_help
4354
exit 1
4455
end
@@ -73,7 +84,10 @@ def version_flags_for(process)
7384
end
7485

7586
# Try to run a process if it's available, with intelligent fallback strategy
76-
# Returns true if process was found and executed, false if not available
87+
# Returns:
88+
# - true if process ran and exited successfully (exit code 0)
89+
# - false if process ran but exited with error (non-zero exit code)
90+
# - nil if process was not found/available
7791
def run_process_if_available(process, args)
7892
# First attempt: try in current context (works for bundled processes)
7993
return system(process, *args) if installed?(process)
@@ -82,7 +96,7 @@ def run_process_if_available(process, args)
8296
return run_process_outside_bundle(process, args) if process_available_in_system?(process)
8397

8498
# Process not available in either context
85-
false
99+
nil
86100
end
87101

88102
# Run a process outside of bundler context

sig/react_on_rails/dev/process_manager.rbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module ReactOnRails
1111

1212
def self.installed_in_current_context?: (String) -> bool
1313
def self.version_flags_for: (String) -> Array[String]
14-
def self.run_process_if_available: (String, Array[String]) -> bool
14+
def self.run_process_if_available: (String, Array[String]) -> (bool | nil)
1515
def self.run_process_outside_bundle: (String, Array[String]) -> bool
1616
def self.process_available_in_system?: (String) -> bool
1717
def self.with_unbundled_context: () { () -> untyped } -> untyped

spec/react_on_rails/dev/process_manager_spec.rb

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,63 @@
7171
end
7272

7373
it "uses foreman when overmind not available and foreman is available" do
74+
expect(described_class).to receive(:run_process_if_available)
75+
.with("overmind", ["start", "-f", "Procfile.dev"]).and_return(nil)
76+
expect(described_class).to receive(:run_process_if_available)
77+
.with("foreman", ["start", "-f", "Procfile.dev"]).and_return(true)
78+
79+
described_class.run_with_process_manager("Procfile.dev")
80+
end
81+
82+
it "exits silently when overmind is installed but exits with error (foreman not found)" do
83+
expect(described_class).to receive(:run_process_if_available)
84+
.with("overmind", ["start", "-f", "Procfile.dev"]).and_return(false)
85+
expect(described_class).to receive(:run_process_if_available)
86+
.with("foreman", ["start", "-f", "Procfile.dev"]).and_return(nil)
87+
expect(described_class).not_to receive(:show_process_manager_installation_help)
88+
expect_any_instance_of(Kernel).to receive(:exit).with(1).and_raise(SystemExit)
89+
90+
expect { described_class.run_with_process_manager("Procfile.dev") }.to raise_error(SystemExit)
91+
end
92+
93+
it "exits silently when overmind fails but foreman succeeds" do
7494
expect(described_class).to receive(:run_process_if_available)
7595
.with("overmind", ["start", "-f", "Procfile.dev"]).and_return(false)
7696
expect(described_class).to receive(:run_process_if_available)
7797
.with("foreman", ["start", "-f", "Procfile.dev"]).and_return(true)
98+
expect(described_class).not_to receive(:show_process_manager_installation_help)
99+
expect_any_instance_of(Kernel).not_to receive(:exit)
78100

79101
described_class.run_with_process_manager("Procfile.dev")
80102
end
81103

82-
it "exits with error when no process manager available" do
104+
it "exits silently when overmind fails and foreman also fails" do
83105
expect(described_class).to receive(:run_process_if_available)
84106
.with("overmind", ["start", "-f", "Procfile.dev"]).and_return(false)
85107
expect(described_class).to receive(:run_process_if_available)
86108
.with("foreman", ["start", "-f", "Procfile.dev"]).and_return(false)
109+
expect(described_class).not_to receive(:show_process_manager_installation_help)
110+
expect_any_instance_of(Kernel).to receive(:exit).with(1).and_raise(SystemExit)
111+
112+
expect { described_class.run_with_process_manager("Procfile.dev") }.to raise_error(SystemExit)
113+
end
114+
115+
it "exits silently when overmind not found but foreman exits with error" do
116+
expect(described_class).to receive(:run_process_if_available)
117+
.with("overmind", ["start", "-f", "Procfile.dev"]).and_return(nil)
118+
expect(described_class).to receive(:run_process_if_available)
119+
.with("foreman", ["start", "-f", "Procfile.dev"]).and_return(false)
120+
expect(described_class).not_to receive(:show_process_manager_installation_help)
121+
expect_any_instance_of(Kernel).to receive(:exit).with(1).and_raise(SystemExit)
122+
123+
expect { described_class.run_with_process_manager("Procfile.dev") }.to raise_error(SystemExit)
124+
end
125+
126+
it "exits with error when no process manager available" do
127+
expect(described_class).to receive(:run_process_if_available)
128+
.with("overmind", ["start", "-f", "Procfile.dev"]).and_return(nil)
129+
expect(described_class).to receive(:run_process_if_available)
130+
.with("foreman", ["start", "-f", "Procfile.dev"]).and_return(nil)
87131
expect(described_class).to receive(:show_process_manager_installation_help)
88132
expect_any_instance_of(Kernel).to receive(:exit).with(1)
89133

@@ -117,12 +161,12 @@
117161
expect(result).to be true
118162
end
119163

120-
it "returns false when process not available anywhere" do
164+
it "returns nil when process not available anywhere" do
121165
allow(described_class).to receive(:installed?).with("nonexistent").and_return(false)
122166
allow(described_class).to receive(:process_available_in_system?).with("nonexistent").and_return(false)
123167

124168
result = described_class.send(:run_process_if_available, "nonexistent", ["start"])
125-
expect(result).to be false
169+
expect(result).to be_nil
126170
end
127171
end
128172

0 commit comments

Comments
 (0)