Conversation
| bin/shakapacker "$@" & | ||
| child_pid=$! |
There was a problem hiding this comment.
Narrow race condition: There is a window between the & (line 17) and child_pid=$! (line 18) where a signal can arrive. If INT or TERM lands in that window, cleanup runs with child_pid="", the guard [ -n "${child_pid}" ] is false, and the function calls exit 0 — leaving the already-launched bin/shakapacker child as an orphan.
The window is tiny, but in a busy CI environment it can be hit. A common mitigation is to delay trapping the signal until after child_pid is captured:
| bin/shakapacker "$@" & | |
| child_pid=$! | |
| bin/shakapacker "$@" & | |
| child_pid=$! | |
| trap cleanup INT TERM |
This is simpler than the current placement and eliminates the race entirely (signals before the background launch are still handled by the default shell behaviour, which is the same exit outcome).
If keeping the early trap, an alternative is to check-and-kill in a loop or use a wait with trap ... && kill $child_pid pattern.
Greptile SummaryThis PR eliminates noisy Ruby interrupt backtraces when pressing Ctrl-C to stop
Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant bin/dev
participant ProcessManager
participant Overmind/Foreman
participant shakapacker-watch
participant shakapacker
User->>bin/dev: Start
bin/dev->>ProcessManager: run_with_process_manager("Procfile.dev")
ProcessManager->>Overmind/Foreman: system("overmind", "start", ...)
Overmind/Foreman->>shakapacker-watch: Launch watcher process
shakapacker-watch->>shakapacker: bin/shakapacker "$@" & (backgrounded)
shakapacker-watch->>shakapacker-watch: wait $child_pid
User->>Overmind/Foreman: Ctrl-C (SIGINT)
Overmind/Foreman->>shakapacker-watch: SIGINT/SIGTERM
shakapacker-watch->>shakapacker-watch: trap fires cleanup()
shakapacker-watch->>shakapacker: kill -TERM
shakapacker-->>shakapacker-watch: exits
shakapacker-watch->>shakapacker-watch: exit 0 (clean)
Overmind/Foreman-->>ProcessManager: Interrupt raised
ProcessManager->>ProcessManager: rescue Interrupt → true
ProcessManager-->>bin/dev: Clean exit (no backtrace)
Last reviewed commit: ddf61ce |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ddf61ce716
ℹ️ 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".
|
|
||
| # React on Rails Pro - RSC bundle watcher | ||
| rsc-bundle: RSC_BUNDLE_ONLY=yes bin/shakapacker --watch | ||
| rsc-bundle: RSC_BUNDLE_ONLY=yes bin/shakapacker-watch --watch |
There was a problem hiding this comment.
Ensure RSC generator installs shakapacker-watch
This line switches the RSC watcher to bin/shakapacker-watch --watch, but standalone rails g react_on_rails:rsc only appends to Procfile.dev and never creates bin/shakapacker-watch. That standalone path does not run InstallGenerator#add_bin_scripts, so apps that were installed before this commit will get a Procfile entry pointing to a missing executable, and bin/dev/foreman fails with No such file or directory when starting rsc-bundle.
Useful? React with 👍 / 👎.
|
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 (2)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughThe PR introduces a 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 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 |
CLAUDE.mdTool-specific guidance for Claude Code in this repository. Source of Truth
If this file conflicts with Behavioral Defaults
Git Safety
Claude-Specific WorkflowUse these docs for Claude-oriented operational guidance:
For Pro-package specifics, also read |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Missing
bin/shakapacker-watchin spec/dummy directory- Added the missing executable
spec/dummy/bin/shakapacker-watchscript so all updated dummy Procfiles now reference an existing watcher command.
- Added the missing executable
Or push these changes by commenting:
@cursor push aa0c1272de
Preview (aa0c1272de)
diff --git a/react_on_rails/spec/dummy/bin/shakapacker-watch b/react_on_rails/spec/dummy/bin/shakapacker-watch
new file mode 100644
--- /dev/null
+++ b/react_on_rails/spec/dummy/bin/shakapacker-watch
@@ -1,0 +1,27 @@
+#!/usr/bin/env sh
+set -eu
+
+child_pid=""
+
+cleanup() {
+ if [ -n "${child_pid}" ] && kill -0 "$child_pid" 2>/dev/null; then
+ kill -TERM "$child_pid" 2>/dev/null || true
+ wait "$child_pid" 2>/dev/null || true
+ fi
+
+ exit 0
+}
+
+trap cleanup INT TERM
+
+bin/shakapacker "$@" &
+child_pid=$!
+
+if wait "$child_pid"; then
+ status=0
+else
+ status=$?
+fi
+
+child_pid=""
+exit "$status"
AGENTS.mdInstructions for AI coding agents working on the React on Rails codebase. React on Rails is a Ruby gem + npm package that integrates React with Ruby on Rails, providing server-side rendering (SSR) via Node.js or ExecJS. This is a monorepo: the open-source gem lives at Canonical Agent Policy
Other agent-facing docs (for example Commands# Install dependencies
bundle && pnpm install
# Build TypeScript → JavaScript
pnpm run build
# Lint (MANDATORY before every commit)
bundle exec rubocop # Ruby — must pass with zero offenses
pnpm run lint # JS/TS via ESLint
pnpm start format.listDifferent # Check Prettier formatting
rake lint # All linting (Ruby + JS + formatting)
# Auto-fix formatting
rake autofix # Preferred for all formatting
# Run tests
rake run_rspec:gem # Ruby unit tests (gem code)
rake run_rspec:dummy # Ruby integration tests (dummy Rails app)
pnpm run test # JavaScript/TypeScript tests
rake # Full suite (lint + all tests except examples)
# Type checking
pnpm run type-check # TypeScript
bundle exec rake rbs:validate # RBS signatures
# Additional test subsets
rake run_rspec # All Ruby tests
rake all_but_examples # All tests except generated examples
rake run_rspec:shakapacker_examples_basic # Single example test
# Full initial setup
bundle && pnpm install && rake shakapacker_examples:gen_all && rake node_package && rake
# CI/workflow linting
actionlint # GitHub Actions lint
yamllint .github/ # YAML lint (do NOT run RuboCop on .yml files)
# Dependency version updates
rake shakapacker:update_version[9.6.1] # Update shakapacker across the monorepoUpdating ShakapackerUse The task handles Ruby version switching for apps that require a different Ruby version (set Testing
Run specific test files: bundle exec rspec react_on_rails/spec/react_on_rails/path/to/spec.rb
cd react_on_rails/spec/dummy && bundle exec rspec spec/path/to/spec.rbProject Structure
Code StyleRuby (RuboCop)Line length max 120 characters. Run Line length — break long chains: # Bad
content = pack_content.gsub(/import.*from.*['"];/, "").gsub(/ReactOnRails\.register.*/, "")
# Good
content = pack_content.gsub(/import.*from.*['"];/, "")
.gsub(/ReactOnRails\.register.*/, "")Named subjects in RSpec: # Bad
subject { instance.method_name(arg) }
# Good
subject(:method_result) { instance.method_name(arg) }Security violations — scope disable comments tightly: # rubocop:disable Security/Eval
expect { evaluate(sanitized_content) }.not_to raise_error
# rubocop:enable Security/EvalJavaScript/TypeScriptPrettier handles all formatting. Never manually format — run Git WorkflowBranch naming: Commit messages: Explain why, not what. One logical change per commit. PR creation: Use Review WorkflowFor small, focused PRs (roughly 5 files changed or fewer and one clear purpose):
BoundariesAlways
Ask First
Never
Key Concept: File Suffixes vs. RSC DirectiveReact on Rails has two independent systems that both use "client" and "server" terminology. Do not confuse them. 1. Bundle Placement (
|
|
test single line |
React on Rails
Package RelationshipsReact on Rails consists of Ruby gems and npm packages that must be paired correctly: Open-Source (base):
Pro (commercial, extends base):
IMPORTANT: When using the Quick SetupBase (open-source):rails generate react_on_rails:installPro with Node Renderer:bundle add react_on_rails_pro
rails generate react_on_rails:install --proPro with React Server Components:bundle add react_on_rails_pro
rails generate react_on_rails:install --rscNode Renderer APIThe Node Renderer is a Fastify-based Node.js server for high-performance SSR. Correct usage:const { reactOnRailsProNodeRenderer } = require('react-on-rails-pro-node-renderer');
reactOnRailsProNodeRenderer({
serverBundleCachePath: path.resolve(__dirname, '.node-renderer-bundles'),
port: 3800,
supportModules: true,
password: process.env.RENDERER_PASSWORD,
workersCount: 3,
logLevel: 'info',
});The function name is Rails-side configuration:# config/initializers/react_on_rails_pro.rb
ReactOnRailsPro.configure do |config|
config.server_renderer = "NodeRenderer"
config.renderer_url = ENV.fetch("REACT_RENDERER_URL", "http://localhost:3800")
config.renderer_password = ENV.fetch("RENDERER_PASSWORD", "devPassword")
config.prerender_caching = true
endKey attributes: Webpack server config requirements (for Pro Node Renderer):// serverWebpackConfig.js output section
libraryTarget: 'commonjs2',
// after output section
serverWebpackConfig.target = 'node';Client-Side Component Registration// With base package
import ReactOnRails from 'react-on-rails';
ReactOnRails.register({ MyComponent });
// With Pro package (use this when react_on_rails_pro gem is installed)
import ReactOnRails from 'react-on-rails-pro';
ReactOnRails.register({ MyComponent });Pro-Exclusive Importsimport { RSCRoute } from 'react-on-rails-pro/RSCRoute';
import registerServerComponent from 'react-on-rails-pro/registerServerComponent/client';
import { wrapServerComponentRenderer } from 'react-on-rails-pro/wrapServerComponentRenderer/client';Documentation Links
|
|
test 2 |
7a4915e to
30668d4
Compare
ddf61ce to
c07a2c1
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c07a2c1e88
ℹ️ 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".
| rails: bundle exec rails s -p ${PORT:-3000} | ||
| dev-server: bin/shakapacker-dev-server | ||
| server-bundle: SERVER_BUNDLE_ONLY=yes bin/shakapacker --watch | ||
| server-bundle: SERVER_BUNDLE_ONLY=yes bin/shakapacker-watch --watch |
There was a problem hiding this comment.
Update watcher auto-detection for wrapped command
Switching the generated server-bundle process to bin/shakapacker-watch --watch breaks ServerManager#shakapacker_watch_process_running?, which still only searches for SERVER_BUNDLE_ONLY=yes bin/shakapacker --watch / bin/shakapacker --watch in react_on_rails/lib/react_on_rails/dev/server_manager.rb (lines 333-345). In apps generated from this template, bin/dev test-watch in --test-watch-mode=auto will no longer detect an existing dev watcher and will incorrectly choose full mode, causing duplicate watch processes in the shared-output workflow this detection is meant to avoid.
Useful? React with 👍 / 👎.
| @@ -1,2 +1,2 @@ | |||
| web: bin/rails server -p ${PORT:-3000} | |||
| js: bin/shakapacker --watch | |||
| js: bin/shakapacker-watch --watch | |||
There was a problem hiding this comment.
Keep doctor static-Procfile detection in sync
This template now uses bin/shakapacker-watch --watch, but Doctor#static_procfile_available? still checks for bin/shakapacker --watch in react_on_rails/lib/react_on_rails/doctor.rb:1442. For newly generated apps, that check returns false even when Procfile.dev-static-assets is present, so the shared-output-path diagnostics can emit the wrong “static Procfile missing” guidance.
Useful? React with 👍 / 👎.
30668d4 to
1da23d5
Compare
c07a2c1 to
054740b
Compare
1da23d5 to
03111e6
Compare
|
Review: Quiet bin/dev shutdown on Ctrl-C Overall this is a clean, well-scoped fix for a real first-run UX problem. The two-pronged approach (shell-level signal forwarding in bin/shakapacker-watch + Ruby-level rescue Interrupt in ProcessManager) is the right architecture. The test cleanup replacing expect_any_instance_of(Kernel) with expect(described_class) is a nice improvement in its own right. Issues 1. cleanup() always exits 0 — no comment explaining why (inline) 2. rescue Interrupt scope is wider than the comment implies (inline) 3. Relative bin/shakapacker path is implicit (inline) Observations (no action needed)
|
size-limit report 📦
|
83c65e5 to
725bcfd
Compare
054740b to
378e1fd
Compare
378e1fd to
b41c506
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Review: Quiet bin/dev shutdown on Ctrl-COverall this is a clean, well-scoped fix. The two-pronged approach (Ruby-level Three things to address: Bug — duplicate PostgreSQL entry in docs
Line 81 currently reads: "If any of the first four are missing…" — this text was accurate before (Node.js, Ruby, Rails, npm/pnpm). With the duplicate PostgreSQL entry the list appears to have five CLI-checked items, which contradicts the prose. Removing the duplicate restores consistency. Minor — template Every other file in Minor — The |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
react_on_rails/spec/react_on_rails/dev/process_manager_spec.rb (1)
126-135: Make this exit-path spec fail-fast for consistency with sibling tests.This case expects
exit(1)but does not raiseSystemExit, unlike similar exit-path examples above. Raising here too makes control-flow intent explicit.Suggested consistency tweak
it "exits with error when no process manager available" do expect(described_class).to receive(:run_process_if_available) .with("overmind", ["start", "-f", "Procfile.dev"]).and_return(nil) expect(described_class).to receive(:run_process_if_available) .with("foreman", ["start", "-f", "Procfile.dev"]).and_return(nil) expect(described_class).to receive(:show_process_manager_installation_help) - expect(described_class).to receive(:exit).with(1) + expect(described_class).to receive(:exit).with(1).and_raise(SystemExit) - described_class.run_with_process_manager("Procfile.dev") + expect { described_class.run_with_process_manager("Procfile.dev") }.to raise_error(SystemExit) 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 126 - 135, The spec "exits with error when no process manager available" should assert that exit(1) raises SystemExit like sibling tests: change the final expectation so that calling described_class.run_with_process_manager("Procfile.dev") is wrapped in an expectation that it raises SystemExit (with status 1) instead of merely expecting described_class to receive(:exit); keep the existing stubs/mocks for run_process_if_available and show_process_manager_installation_help unchanged and ensure the test now asserts the SystemExit is raised.
🤖 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 126-135: The spec "exits with error when no process manager
available" should assert that exit(1) raises SystemExit like sibling tests:
change the final expectation so that calling
described_class.run_with_process_manager("Procfile.dev") is wrapped in an
expectation that it raises SystemExit (with status 1) instead of merely
expecting described_class to receive(:exit); keep the existing stubs/mocks for
run_process_if_available and show_process_manager_installation_help unchanged
and ensure the test now asserts the SystemExit is raised.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d1f048a2-5681-4804-a23a-9c3c493ad3de
📒 Files selected for processing (12)
docs/oss/getting-started/create-react-on-rails-app.mdreact_on_rails/lib/generators/react_on_rails/rsc_setup.rbreact_on_rails/lib/generators/react_on_rails/templates/base/base/Procfile.devreact_on_rails/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assetsreact_on_rails/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-watchreact_on_rails/lib/react_on_rails/dev/process_manager.rbreact_on_rails/spec/dummy/Procfile.devreact_on_rails/spec/dummy/Procfile.dev-static-assetsreact_on_rails/spec/dummy/Procfile.dev.no.turbolinksreact_on_rails/spec/react_on_rails/dev/process_manager_spec.rbreact_on_rails/spec/react_on_rails/generators/install_generator_spec.rbreact_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
✅ Files skipped from review due to trivial changes (8)
- react_on_rails/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets
- docs/oss/getting-started/create-react-on-rails-app.md
- react_on_rails/lib/generators/react_on_rails/templates/base/base/Procfile.dev
- react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
- react_on_rails/spec/dummy/Procfile.dev
- react_on_rails/spec/dummy/Procfile.dev-static-assets
- react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
- react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
🚧 Files skipped from review as they are similar to previous changes (3)
- react_on_rails/spec/dummy/Procfile.dev.no.turbolinks
- react_on_rails/lib/react_on_rails/dev/process_manager.rb
- react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-watch
…mplate - Add bin/shakapacker-watch to spec/dummy/bin/ (was referenced by Procfiles but never created since dummy doesn't use the generator) - Remove duplicate PostgreSQL bullet in create-react-on-rails-app.md - Set executable bit on bin/shakapacker-watch template for consistency with other files in templates/base/base/bin/ Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
|
||
| # Process not available in either context | ||
| nil | ||
| rescue Interrupt |
There was a problem hiding this comment.
The rescue Interrupt covers the entire method body, including the installed? and process_available_in_system? version-check calls, not just the system(process, *args) call that actually runs the process manager. That means if Ctrl-C arrives during the version probe (before overmind/foreman has even started), this still returns true, which causes run_with_process_manager to treat it as "process ran and exited cleanly" and skip trying the next process manager.
This is functionally correct (Ctrl-C is always a clean exit intent), but the return value of true is semantically misleading — it implies the process ran and succeeded. Returning a dedicated sentinel (e.g. nil with an early return in the caller, or a new :interrupted value) would make the intent clearer. At minimum, the comment could say:
Ctrl-C at any point during process-manager setup or execution should exit quietly; returning
trueskips further fallback attempts in the caller.
| wait "$child_pid" 2>/dev/null || true | ||
| fi | ||
|
|
||
| exit 0 |
There was a problem hiding this comment.
cleanup always calls exit 0, discarding the child's actual exit code. This is intentional (a signal-triggered shutdown is considered clean), but it means that if overmind/foreman kills the watcher via TERM for reasons other than a normal shutdown (e.g. it detected the child was misbehaving), the process always reports success to the process manager.
For a dev-only watcher this is acceptable, but a short comment here would help the next reader understand the design choice:
| exit 0 | |
| exit 0 # treat signal-triggered shutdown as clean regardless of child exit code |
|
|
||
| trap cleanup INT TERM | ||
|
|
||
| bin/shakapacker "$@" & |
There was a problem hiding this comment.
The script uses #!/usr/bin/env sh (POSIX sh), but POSIX does not guarantee that a signal delivered during wait will cause wait to return with the trap executing before the next command. In bash and dash (the common /bin/sh on Ubuntu/macOS) this works as expected — the signal interrupts wait, the trap runs and calls exit 0, and the if wait block is never reached. However, on stricter POSIX shells the timing can vary.
Given that Rails developers predominantly use macOS (bash/zsh → /bin/sh is usually bash 3.x or a BSD sh) and Ubuntu (dash), this is fine in practice. Just worth noting in case the script is ever ported to more exotic environments.
ReviewOverall this is a clean, well-scoped fix. The approach is sound and the added test coverage is solid.
|
Move trap installation after child_pid=$! so a signal arriving between fork and assignment cannot leave cleanup() with an empty pid. Before the trap is registered, default signal behavior (immediate exit) applies, which is the desired outcome anyway. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
|
||
| bin/shakapacker "$@" & | ||
| child_pid=$! | ||
| trap cleanup INT TERM |
There was a problem hiding this comment.
Race window between child start and trap setup.
Lines 15–17 read:
bin/shakapacker "$@" &
child_pid=$!
trap cleanup INT TERMIf SIGINT or SIGTERM is delivered in the narrow window after the & fork but before the trap line executes, the default signal disposition applies and the child process is left running (orphaned) while the wrapper exits without forwarding TERM or waiting.
Consider setting the trap before forking:
| trap cleanup INT TERM | |
| trap cleanup INT TERM | |
| bin/shakapacker "$@" & | |
| child_pid=$! |
With this ordering, cleanup can still handle the case gracefully because child_pid starts as "" and the guard [ -n "${child_pid}" ] prevents a kill attempt before the PID is recorded. The window effectively vanishes for TERM, and the SIGINT window shrinks to a single $! assignment.
| wait "$child_pid" 2>/dev/null || true | ||
| fi | ||
|
|
||
| exit 0 |
There was a problem hiding this comment.
cleanup always exits 0, masking non-Ctrl-C failures.
The function is shared between the INT and TERM trap. When overmind/foreman sends SIGTERM because a different Procfile process crashed (not because the user pressed Ctrl-C), this wrapper silently exits 0, hiding the fact that the watcher was force-killed. A developer running bin/dev after a webpack crash would see no indication that the watcher was terminated abnormally.
One option is to forward the original signal so the process exits with the conventional signal status:
cleanup() {
if [ -n "${child_pid}" ] && kill -0 "$child_pid" 2>/dev/null; then
kill -TERM "$child_pid" 2>/dev/null || true
wait "$child_pid" 2>/dev/null || true
fi
exit 0 # keep quiet-exit for Ctrl-C (INT); consider re-raising TERM for error cases
}This is a minor UX trade-off rather than a bug — just worth a conscious decision. If quiet exit on both INT and TERM is the desired behaviour, a brief comment here would make the intent clear to future maintainers.
| nil | ||
| rescue Interrupt | ||
| # Ctrl-C during overmind/foreman shutdown should exit quietly. | ||
| true |
There was a problem hiding this comment.
rescue Interrupt returning true correctly triggers clean exit — one edge to be aware of.
Returning true here causes run_with_process_manager to return (line 51/54) rather than calling exit 1, so bin/dev exits with code 0. This is the right behaviour for interactive Ctrl-C.
The one scenario worth noting: if installed? (which calls system internally) raises Interrupt — i.e., the user hits Ctrl-C before the process manager even launches — this rescue also fires and returns true. The caller then sees "process ran successfully" even though it never ran at all. In practice this is harmless for bin/dev (quiet exit is still the right outcome), but if any future caller of run_process_if_available distinguishes true from nil for logic beyond exit-code handling, the distinction would be silently lost.
A tiny comment on the rescue clause would make the intent explicit:
| true | |
| rescue Interrupt | |
| # Ctrl-C during process-manager startup or execution: treat as a clean exit. | |
| # Returning true (rather than re-raising) suppresses both the Ruby backtrace | |
| # and the "process not found" error path in run_with_process_manager. | |
| true |
|
Overall this is clean, well-motivated work. The two-layer fix is the right approach, and the test improvements replacing expect_any_instance_of(Kernel) with expect(described_class) are a genuine quality uplift. bin/shakapacker-watch is emitted by the generator via Thor's directory action in install_generator.rb line 347, which bulk-copies everything under templates/base/base/bin/. bin_scripts_to_chmod uses Dir.children on that same path (line 400) so the file is also chmod 755 automatically. No changes to the explicit base_files list in base_generator.rb are needed. Three inline issues flagged:
None of these block merging. |
|
The With that merged, this PR can drop the |
## Summary - Adds a new `bin/shakapacker-watch` shell script binstub that traps INT/TERM signals and forwards TERM to the underlying `bin/shakapacker` process for clean shutdown - Updates dummy app Procfiles to use `bin/shakapacker-watch --watch` instead of `bin/shakapacker --watch` - The script is automatically installed alongside `bin/shakapacker` and `bin/shakapacker-dev-server` via `rake shakapacker:binstubs` ## Why When using `bin/dev` with overmind or foreman, pressing Ctrl-C causes Ruby interrupt backtraces from watcher processes. This is a bad first-run experience. The wrapper script absorbs the INT signal and sends a clean TERM to the child process. Related: shakacode/react_on_rails#2652 — this PR moves the `bin/shakapacker-watch` wrapper from react_on_rails into shakapacker where it belongs, since shakapacker already generates the other bin scripts. ## Test plan - [ ] `sh -n lib/install/bin/shakapacker-watch` passes syntax check - [ ] `bundle exec rubocop` passes - [ ] `yarn lint` passes - [ ] Existing `bundle exec rspec spec/shakapacker/` tests pass (979/981, 2 pre-existing failures unrelated to this change) - [ ] `rake shakapacker:binstubs` copies `bin/shakapacker-watch` to the app - [ ] `bin/dev` with Ctrl-C exits cleanly without Ruby backtraces 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: adds a new optional binstub and updates docs/examples; core compilation/runtime logic is unchanged aside from doctor reporting an extra expected binstub. > > **Overview** > Adds a new `bin/shakapacker-watch` shell wrapper binstub that runs `bin/shakapacker` and traps `INT`/`TERM` to terminate the child process cleanly (avoiding interrupt backtraces in Procfile runners like `bin/dev`). > > Updates docs and diagnostics to recognize/recommend the new binstub (`README`, `CHANGELOG`, and `Shakapacker::Doctor` binstub reporting), and switches the dummy app Procfiles (plus dummy binstub) to use `shakapacker-watch` for `--watch` workflows. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 210eac5. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a shakapacker-watch binstub to run watch mode and forward termination signals, exiting with the underlying process status. * **Documentation** * Updated README and CHANGELOG with usage guidance and Procfile recommendation to use shakapacker-watch --watch. * **Chores** * Updated example Procfiles and dev fixtures to invoke the new binstub; health checks now consider the watch binstub. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>


Summary
Interruptaround the overmind/foreman system call sobin/devexits quietly on Ctrl-Cbin/shakapacker-watchwrapper and switch Procfiles to use it for watcher processesWhy
During the fresh-app validation runs,
bin/devshut down successfully but printed Ruby interrupt backtraces from both the main process manager and theserver-bundlewatcher. That is a bad first-run experience immediately after following the docs.What changed
ProcessManager.run_process_if_availablenow treats Ctrl-C during process-manager shutdown as a clean exit pathbin/shakapacker-watchtrapsINT/TERM, forwardsTERMtobin/shakapacker, and waits quietlyProcfile.dev,Procfile.dev-static-assets, and the RSC watcher insertion now usebin/shakapacker-watch --watchspec/dummyProcfiles to reflect the new watcher pathprocess_manager_specto stubdescribed_class.systemdirectly instead of using flakyexpect_any_instance_of(Kernel)expectationsValidation
bundle exec rspec react_on_rails/spec/react_on_rails/dev/process_manager_spec.rbbundle exec rspec react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:39sh -n react_on_rails/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-watchbin/devwith the patched gem code + watcher wrapper and confirmed Ctrl-C exits cleanly with no Ruby backtraceNotes
react_on_rails_pro (~> 16.4.0)gem version required by that example group.Note
Low Risk
Low risk dev-workflow change: adjusts signal/interrupt handling and Procfile watcher commands, with minimal impact outside local development but potential for minor behavior differences in process shutdown.
Overview
Improves local
bin/devshutdown by treatingInterruptduring overmind/foreman execution as a clean exit inReactOnRails::Dev::ProcessManager(avoids noisy backtraces on Ctrl-C).Updates generated Procfiles (including RSC watcher insertion) to run watchers via a new
bin/shakapacker-watchwrapper that trapsINT/TERMand forwards termination to the underlyingbin/shakapacker --watchprocess. Generator and dummy/spec fixtures are updated accordingly, andprocess_manager_specstubsdescribed_class.systemdirectly for more reliable expectations.Written by Cursor Bugbot for commit 951e126. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
New Features
Bug Fixes