Skip to content

Add bin/shakapacker-watch binstub for clean Ctrl-C shutdown#1026

Merged
justin808 merged 7 commits into
mainfrom
jg/add-bin-shakapacker-watch
Mar 29, 2026
Merged

Add bin/shakapacker-watch binstub for clean Ctrl-C shutdown#1026
justin808 merged 7 commits into
mainfrom
jg/add-bin-shakapacker-watch

Conversation

@justin808

@justin808 justin808 commented Mar 26, 2026

Copy link
Copy Markdown
Member

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


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.

Written by Cursor Bugbot for commit 210eac5. This will update automatically on new commits. Configure here.

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.

The new wrapper script traps INT/TERM signals, forwards TERM to the
underlying bin/shakapacker process, and waits quietly. This prevents
Ruby interrupt backtraces when stopping Procfile-based dev workflows
(e.g., bin/dev with overmind/foreman).

Use bin/shakapacker-watch --watch in Procfiles instead of
bin/shakapacker --watch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown

Walkthrough

A new shell wrapper binstub bin/shakapacker-watch was added that spawns shakapacker --watch, traps INT/TERM, forwards TERM to the child with a timeout and escalation, and exits with the child's status. Documentation, Procfiles, a dummy binstub, and a fixture dependency version were updated.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added Unreleased entry documenting bin/shakapacker-watch and its INT/TERM trapping/forwarding behavior.
Wrapper script
lib/install/bin/shakapacker-watch
New POSIX shell wrapper: -h/--help, resolves script dir, launches shakapacker in background, traps INT/TERM, forwards TERM, polls then escalates to KILL if needed, mirrors child's exit status.
Dev Procfiles & dummy binstub
spec/dummy/Procfile.dev, spec/dummy/Procfile.dev-static, spec/dummy/Procfile.dev-static-assets, spec/dummy/bin/shakapacker-watch
Procfile entries updated to call bin/shakapacker-watch --watch; added dummy wrapper referencing the install script.
Documentation
README.md
Documented ./bin/shakapacker-watch as a wrapper that traps signals; added examples showing wrapper vs direct watcher usage.
Fixture deps
spec/dummy/package.json
Bumped shakapacker dependency from 9.6.1 to 9.7.0.
Doctor checks
lib/shakapacker/doctor.rb
Updated binstub detection to include bin/shakapacker-watch in reported binstubs.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Foreman as Foreman / Procfile
participant WatchBin as bin/shakapacker-watch
participant Shaka as bin/shakapacker
Foreman->>WatchBin: start "shakapacker-watch --watch"
WatchBin->>Shaka: spawn "shakapacker --watch" (background)
Note right of WatchBin: records child PID
Foreman->>WatchBin: send INT/TERM
WatchBin->>Shaka: on TERM -> send TERM to child
WatchBin->>WatchBin: poll up to ~5s for exit
alt child still running
WatchBin->>Shaka: send KILL
end
WatchBin->>Shaka: wait; capture exit status
WatchBin->>Foreman: exit with child's status

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped a script to mind the pack,
Trapped INT and TERM upon the track,
I nudge the child to end with grace,
Or tap the door with killing pace,
Then bow and pass along its pace.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a bin/shakapacker-watch binstub to handle clean signal shutdown. It is specific and directly related to the primary purpose of the changeset.

✏️ 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/add-bin-shakapacker-watch

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 lib/install/bin/shakapacker-watch Outdated
Comment thread lib/install/bin/shakapacker-watch
Comment thread lib/install/bin/shakapacker-watch
Comment thread lib/install/bin/shakapacker-watch Outdated
Comment thread lib/install/bin/shakapacker-watch Outdated
@claude

claude Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Review

The approach is sound - a POSIX shell wrapper that traps signals and forwards TERM to the child is the right way to suppress Ruby interrupt backtraces in Procfile-based dev workflows. The binstub wiring via lib/install/bin/ and the rake task is consistent with the existing pattern for bin/shakapacker and bin/shakapacker-dev-server.

Three issues in the shell script worth addressing (inline comments added):

  1. Relative path (bin/shakapacker, line 15) - will break if the script is invoked from any directory other than the project root. Using the dirname-based path makes it robust everywhere.

  2. Signal trap registered after child spawn (line 17) - tiny but real race: a Ctrl-C that arrives between the background launch and the trap call falls through to the default handler, leaving the child orphaned. Moving the trap to before the launch line closes it; the existing guard in cleanup already handles the not-yet-set case.

  3. No timeout in cleanup (lines 6-13) - wait after kill -TERM has no upper bound. If the webpack/rspack watcher ignores TERM, the terminal appears to hang on Ctrl-C. A short sleep loop + SIGKILL escalation (see inline suggestion) gives users a predictable exit.

Minor nit: spec/dummy/bin/shakapacker-watch is a verbatim copy of lib/install/bin/shakapacker-watch. If the two ever drift this will cause confusion. It might be worth adding a comment noting it is generated by rake shakapacker:binstubs, or generating it as part of CI setup rather than committing it.

@greptile-apps

greptile-apps Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a bin/shakapacker-watch POSIX shell wrapper that absorbs SIGINT (Ctrl-C) and forwards SIGTERM to the underlying bin/shakapacker child process, preventing Ruby interrupt backtraces in Procfile-based workflows (foreman/overmind). The binstub is added to lib/install/bin/ so it is automatically copied by rake shakapacker:binstubs, and the three dummy-app Procfiles are updated to use it.

Key points:

  • The approach — backgrounding the real process with &, trapping INT/TERM, and forwarding TERM — is a well-established POSIX shell pattern and fits naturally alongside the existing binstubs.
  • There is one P1 logic issue: trap cleanup INT TERM is registered on line 17, after the child has already been started on line 15. If a signal arrives in that startup window the parent exits with default shell behavior, orphaning the background child. Moving the trap before the & fork fixes this (applies identically to both lib/install/bin/shakapacker-watch and spec/dummy/bin/shakapacker-watch).
  • The CHANGELOG entry contains an unfilled #NNN placeholder — it should read #1026.
  • The child is invoked via the relative path bin/shakapacker, which works correctly for all Procfile-based invocations (foreman/overmind always sets CWD to the project root) but differs from the absolute-path pattern used by the other Ruby binstubs. A $(dirname "$0") anchor would make it location-independent.

Confidence Score: 4/5

Safe to merge after fixing the trap ordering and the #NNN changelog placeholder.

The overall design is sound and the primary use case (Procfile + Ctrl-C) is handled correctly because the trap IS in place before the long-running wait. The P1 trap-ordering issue only affects the tiny startup window and is easily fixed with a one-line reorder. The two P2 items (changelog placeholder, relative path) are cosmetic or low-risk. Once the trap is moved before the &, the PR is ready to merge.

lib/install/bin/shakapacker-watch and spec/dummy/bin/shakapacker-watch (identical fix needed in both for trap ordering)

Important Files Changed

Filename Overview
lib/install/bin/shakapacker-watch New POSIX sh wrapper that traps INT/TERM and forwards TERM to child; trap is registered after forking, which is a race condition that could orphan the child in the startup window.
spec/dummy/bin/shakapacker-watch Identical copy of lib/install/bin/shakapacker-watch for the dummy app; carries the same trap-ordering and relative-path issues.
CHANGELOG.md New Unreleased entry added correctly under ### Added; contains a #NNN placeholder that should be updated to #1026.
spec/dummy/Procfile.dev Switches wp-server from bin/shakapacker --watch to bin/shakapacker-watch --watch; straightforward and correct.
spec/dummy/Procfile.dev-static Updates webpack entry to use bin/shakapacker-watch -w; functionally equivalent to the previous invocation.
spec/dummy/Procfile.dev-static-assets Updates js entry to use bin/shakapacker-watch --watch; straightforward change.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[bin/shakapacker-watch invoked] --> B["bin/shakapacker & (background)"]
    B --> C["child_pid=$!"]
    C --> D["trap cleanup INT TERM ⚠️ set AFTER fork"]
    D --> E["wait $child_pid"]

    E -->|"child exits 0"| F["status=0"]
    E -->|"child exits non-zero"| G["status=$?"]
    E -->|"INT or TERM received"| H["cleanup()"]

    F --> I["child_pid=''"]
    G --> I
    I --> J["exit $status"]

    H --> K{"child_pid set?"}
    K -->|yes| L["kill -TERM $child_pid"]
    K -->|no| N["exit 0"]
    L --> M["wait $child_pid"]
    M --> N

    style D fill:#ffcccc,stroke:#cc0000
Loading

Reviews (1): Last reviewed commit: "Update CHANGELOG with actual PR number" | Re-trigger Greptile

Comment thread lib/install/bin/shakapacker-watch Outdated
Comment thread CHANGELOG.md
Comment thread lib/install/bin/shakapacker-watch Outdated
Comment thread lib/install/bin/shakapacker-watch Outdated
Comment thread lib/install/bin/shakapacker-watch Outdated
Comment thread lib/install/bin/shakapacker-watch
@claude

claude Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Review of shakapacker-watch binstub. The approach is sound - wrapping the watcher in a shell script that traps signals is a well-established pattern for clean Procfile shutdown. A few issues worth addressing before merge. 1. Relative path: the child is invoked with a bare relative path, so the script only works when run from the app root. Using dirname-based resolution makes it portable (see inline comment). 2. Trap set after child launch: there is a narrow race window between launching the child and installing the trap. Moving the trap call before the child launch closes it (see inline comment). 3. Cleanup exits 0 on TERM: the cleanup handler always exits 0, masking signal-killed termination from supervisors. The POSIX fix is to reset the trap and re-raise the signal (see inline comment). Minor: check_binstubs.rake does not check for bin/shakapacker-watch (optional but worth adding). The spec/dummy copy is consistent with other dummy bin scripts. No automated signal-handling tests, but sh -n is reasonable for a shell script. Overall the PR solves a real UX pain point. Main ask is fixing path resolution and trap ordering.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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.

Autofix Details

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

  • ✅ Fixed: Signal trap set after child process starts
    • Moved INT/TERM trap installation before child launch in both shakapacker-watch scripts to eliminate the startup signal race window.

Create PR

Or push these changes by commenting:

@cursor push 1f46d05a85
Preview (1f46d05a85)
diff --git a/lib/install/bin/shakapacker-watch b/lib/install/bin/shakapacker-watch
--- a/lib/install/bin/shakapacker-watch
+++ b/lib/install/bin/shakapacker-watch
@@ -12,9 +12,9 @@
   exit 0
 }
 
+trap cleanup INT TERM
 bin/shakapacker "$@" &
 child_pid=$!
-trap cleanup INT TERM
 
 if wait "$child_pid"; then
   status=0

diff --git a/spec/dummy/bin/shakapacker-watch b/spec/dummy/bin/shakapacker-watch
--- a/spec/dummy/bin/shakapacker-watch
+++ b/spec/dummy/bin/shakapacker-watch
@@ -12,9 +12,9 @@
   exit 0
 }
 
+trap cleanup INT TERM
 bin/shakapacker "$@" &
 child_pid=$!
-trap cleanup INT TERM
 
 if wait "$child_pid"; then
   status=0

You can send follow-ups to this agent here.

Comment thread lib/install/bin/shakapacker-watch
…tdown

Use script_dir for reliable sibling script resolution instead of bare
bin/shakapacker. Move trap before spawning the background process so
signals are never missed. Add a 5-second graceful wait loop with SIGKILL
fallback so orphan webpack processes cannot survive Ctrl-C.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread lib/install/bin/shakapacker-watch
Comment thread lib/install/bin/shakapacker-watch
@claude

claude Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Review: Add bin/shakapacker-watch binstub

The approach is sound — absorbing INT/TERM in a shell wrapper and forwarding TERM to the child is the right pattern for Procfile setups. The signal-handling logic is correct: wait is interrupted by the signal, the trap fires, the child is killed with a grace period + SIGKILL fallback, and exit 0 is called before any code below wait can run.

Issues

1. doctor.rb not updated

lib/shakapacker/doctor.rb has two hard-coded binstub lists that do not include shakapacker-watch:

  • check_binstub (~line 391) — reports action-required if binstubs are missing
  • print_binstub_status (~line 1013) — prints the binstubs-found summary

Users who upgrade and forget to re-run rake shakapacker:binstubs will not get a warning. At minimum, print_binstub_status should list bin/shakapacker-watch so the output stays accurate.

2. check_binstubs.rake not updated

lib/tasks/shakapacker/check_binstubs.rake only verifies bin/shakapacker and bin/shakapacker-dev-server. If the Procfile examples are now the canonical recommendation, the task advice message (which tells users what to keep out of .gitignore) is incomplete.

Nits

  • sleep 0.1 is not strictly POSIX (covered inline), but fine for real-world targets.
  • exit 0 on signal is intentional but unconventional; a brief comment in the script would help future readers understand the trade-off.
  • The spec/dummy/bin/shakapacker-watch duplicate is consistent with how other binstubs are represented in the dummy app.
  • CHANGELOG entry is well-formed and complete.

@justin808

Copy link
Copy Markdown
Member Author

Re: adding bin/shakapacker-watch to check_binstubs.rake — declining this suggestion. The check_binstubs task calls exit\! on missing files, so adding shakapacker-watch would break every existing installation that upgrades shakapacker before running the install generator. Unlike bin/shakapacker and bin/shakapacker-dev-server, the watch binstub is optional — apps work fine without it.

Prevents the two identical files from silently drifting apart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread lib/install/bin/shakapacker-watch
Comment thread spec/dummy/bin/shakapacker-watch

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@lib/install/bin/shakapacker-watch`:
- Around line 7-24: cleanup() can run before child_pid is assigned causing the
newly spawned watcher to survive; add a shutdown coordination flag (e.g.,
shutdown_requested) and modify cleanup() to set this flag and return immediately
when child_pid is empty, and then after the watcher is started and child_pid is
assigned check shutdown_requested and, if set, perform the same termination
sequence (kill -TERM/KILL and wait) to ensure the child is always stopped;
reference the cleanup() function and the child_pid and shutdown_requested
variables when implementing the fix.
🪄 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: 402aab34-8049-4576-a7eb-2479fef862cf

📥 Commits

Reviewing files that changed from the base of the PR and between fc0f9a2 and 53dfae6.

⛔ Files ignored due to path filters (2)
  • spec/dummy/package-lock.json is excluded by !**/package-lock.json
  • spec/dummy/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (7)
  • CHANGELOG.md
  • lib/install/bin/shakapacker-watch
  • spec/dummy/Procfile.dev
  • spec/dummy/Procfile.dev-static
  • spec/dummy/Procfile.dev-static-assets
  • spec/dummy/bin/shakapacker-watch
  • spec/dummy/package.json

Comment thread lib/install/bin/shakapacker-watch
@claude

claude Bot commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Review: Add bin/shakapacker-watch binstub

The overall approach is sound — a POSIX sh wrapper that traps INT/TERM and forwards TERM to the child is the right pattern for clean Ctrl-C shutdown in Procfile-based workflows. A few issues to address:

Signal exit code (inline comment on line 22)

The cleanup function always does exit 0, which makes the watcher look like it completed successfully even when killed by a signal. The conventional behaviour is to re-raise after cleanup so the parent process (foreman/overmind) and CI see the correct exit code. See inline suggestion.

check_binstubs not updated

lib/tasks/shakapacker/check_binstubs.rake verifies bin/shakapacker and bin/shakapacker-dev-server but not bin/shakapacker-watch. Since the updated Procfile templates now rely on bin/shakapacker-watch, users who have not run rake shakapacker:binstubs will get a cryptic shell error instead of the helpful binstubs diagnostic. The task and its error message body should include the new script:

verify_file_existence("bin/shakapacker")
verify_file_existence("bin/shakapacker-dev-server")
verify_file_existence("bin/shakapacker-watch")

Minor

  • The symlink in spec/dummy/bin/shakapacker-watch is inconsistent with the other dummy binstubs (real files). See inline comment. Not blocking, but worth aligning.
  • No automated tests for the new binstub. At minimum a spec or rake task could run sh -n lib/install/bin/shakapacker-watch (syntax check) and verify the file is copied by rake shakapacker:binstubs.

Document the new shakapacker-watch binstub in the README Development
section (update binstub count, add to Common Development Commands)
and add --help/-h flag handling to the shell script with usage info,
examples, and a Procfile snippet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread lib/install/bin/shakapacker-watch
Comment thread lib/install/bin/shakapacker-watch
Comment thread lib/install/bin/shakapacker-watch
Comment thread spec/dummy/bin/shakapacker-watch
@claude

claude Bot commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Review: Add bin/shakapacker-watch binstub

Overall: The approach is sound — a shell wrapper that traps INT/TERM and forwards TERM to the child is the right way to absorb Ctrl-C noise in Procfile workflows. The binstub slots cleanly into the existing rake shakapacker:binstubs installation mechanism. One must-fix race condition, two low-severity observations.


Must fix

child_pid assignment race (lines 57–58): The trap is now correctly set before the & fork (good — the earlier version had this backwards). However there is still a window: if INT/TERM arrives between the & and child_pid=$!, cleanup() fires with child_pid="", skips the kill block, and exits — leaving the spawned process orphaned. The fix is a shutdown_requested flag checked immediately after the assignment (see inline comment). CodeRabbit's latest review flagged the same issue.


Low severity / informational

  • sleep 0.1 is non-POSIX (line 43): fractional seconds work on Linux/macOS but not on strict POSIX or BusyBox. Low risk for a Rails dev tool, but worth noting.
  • cleanup() always exits 0 (line 53): correct for interactive Ctrl-C, but process supervisors that inspect exit status won't distinguish a clean stop from a forced SIGKILL. Fine for the stated Procfile use case; worth a comment if the script is documented for broader use.
  • spec/dummy/bin/shakapacker-watch symlink: the relative symlink works correctly for the dummy fixture; the installed binstub uses dirname at runtime so user apps are unaffected.

Not an issue

  • set -eu is handled correctly — kill calls are all guarded with || true or inside conditionals.
  • script_dir resolution via CDPATH= cd -- ... is the right POSIX-safe idiom.
  • Binstub is automatically included in rake shakapacker:binstubs because binstubs.rb copies the entire lib/install/bin/ directory.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lib/install/bin/shakapacker-watch (1)

42-42: Minor portability note: sleep 0.1 is not strictly POSIX.

Fractional seconds work with GNU coreutils and macOS, but may fail on minimal/older shells. If broad portability is a goal, consider a fallback or use integer sleeps (e.g., 1-second intervals with fewer iterations).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/install/bin/shakapacker-watch` at line 42, In shakapacker-watch, the
fractional call "sleep 0.1" is not POSIX-portable; change it to try a fractional
sleep and fall back to an integer sleep when the shell's sleep doesn't support
fractions (e.g. attempt "sleep 0.1" and if it fails, call "sleep 1"), keeping
the same behavior loop structure — locate the "sleep 0.1" token and replace it
with a small fallback wrapper (try fractional sleep and on error use integer
sleep).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/install/bin/shakapacker-watch`:
- Line 42: In shakapacker-watch, the fractional call "sleep 0.1" is not
POSIX-portable; change it to try a fractional sleep and fall back to an integer
sleep when the shell's sleep doesn't support fractions (e.g. attempt "sleep 0.1"
and if it fails, call "sleep 1"), keeping the same behavior loop structure —
locate the "sleep 0.1" token and replace it with a small fallback wrapper (try
fractional sleep and on error use integer sleep).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f370fbaf-d309-4f36-ac7e-f28c7b2b6797

📥 Commits

Reviewing files that changed from the base of the PR and between 53dfae6 and 16137b0.

📒 Files selected for processing (2)
  • README.md
  • lib/install/bin/shakapacker-watch

@justin808

Copy link
Copy Markdown
Member Author

Added bin/shakapacker-watch to print_binstub_status in doctor.rb so the doctor output includes it. Deliberately not adding it to check_binstub since the watch binstub is optional — apps work fine without it and flagging it as missing would be misleading for existing installations.

- Add shutdown_requested flag to close race window between child launch
  and PID assignment in shakapacker-watch
- Add bin/shakapacker-watch to doctor.rb print_binstub_status
  (informational only, not check_binstub since the watch binstub is optional)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lib/install/bin/shakapacker-watch (1)

46-50: Minor portability consideration: fractional sleep argument.

sleep 0.1 requires GNU coreutils or similar; POSIX only guarantees integer seconds. This works on Linux and macOS but may fail on minimal BusyBox or older systems. Given this is a development-time script (Procfile workflows), the impact is low.

💡 Alternative using a portable fallback

If broader portability is desired:

     while [ "$i" -lt 50 ] && kill -0 "$child_pid" 2>/dev/null; do
-      sleep 0.1
+      sleep 1 2>/dev/null || sleep 0.1
       i=$((i + 1))
     done

Or simply increase the poll count with integer sleeps. However, keeping sleep 0.1 is reasonable for typical developer environments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/install/bin/shakapacker-watch` around lines 46 - 50, The loop uses a
fractional sleep (sleep 0.1) which is not guaranteed by POSIX; change it to an
integer-second sleep and adjust the loop count to preserve the same total wait
time. For example, in the while loop that checks kill -0 "$child_pid" (the block
using variables i and child_pid), replace sleep 0.1 and i<50 with sleep 1 and
i<5 (or another integer pair that totals ~5 seconds) so the script remains
portable on BusyBox/older systems while keeping the same timeout behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/install/bin/shakapacker-watch`:
- Around line 46-50: The loop uses a fractional sleep (sleep 0.1) which is not
guaranteed by POSIX; change it to an integer-second sleep and adjust the loop
count to preserve the same total wait time. For example, in the while loop that
checks kill -0 "$child_pid" (the block using variables i and child_pid), replace
sleep 0.1 and i<50 with sleep 1 and i<5 (or another integer pair that totals ~5
seconds) so the script remains portable on BusyBox/older systems while keeping
the same timeout behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c875638e-6b1f-421f-b060-80b937f7f10a

📥 Commits

Reviewing files that changed from the base of the PR and between 16137b0 and 210eac5.

📒 Files selected for processing (2)
  • lib/install/bin/shakapacker-watch
  • lib/shakapacker/doctor.rb

@claude

claude Bot commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Overall: looks solid. The signal-handling logic is correct and the use case is well-motivated. Signal handling details: trap is set before the background process spawns (no missed signals), the shutdown_requested flag closes the race window between the background launch and child_pid assignment, and the SIGKILL fallback after the grace period prevents orphaned webpack processes. The portable CDPATH+dirname idiom for script_dir is correct. One portability note: sleep 0.1 (fractional seconds) is not POSIX-compliant. It works on GNU coreutils and macOS but errors on Alpine Linux / BusyBox ash, which is common in Docker-based Rails setups. sleep 1 with a loop limit of 5 would be fully portable and still give a 5-second grace window. The cleanup function always exits with code 0 on signal — right for Ctrl-C but means SIGTERM also masks any non-zero webpack exit. Fine for a dev tool, just worth being aware of. The decision to add shakapacker-watch to print_binstub_status but not check_binstub is correct since the binstub is optional. The script name implies watch mode but --watch must still be passed explicitly; the docs and examples are clear about this.

kill -TERM "$child_pid" 2>/dev/null || true
i=0
while [ "$i" -lt 50 ] && kill -0 "$child_pid" 2>/dev/null; do
sleep 0.1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sleep 0.1 uses fractional seconds, which is not POSIX-compliant. It works on GNU coreutils (Linux) and macOS but errors on Alpine Linux / BusyBox ash — common in Docker-based Rails setups. A portable alternative that keeps the same 5-second total grace window:

Suggested change
sleep 0.1
sleep 1
i=$((i + 1))
done
if kill -0 "$child_pid" 2>/dev/null; then

Change the loop bound from 50 to 5 alongside this to match the original timeout.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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.

Fix All in Cursor

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

  • ✅ Fixed: Optional binstub prevents "All binstubs found" doctor message
    • Updated doctor binstub status reporting to require only the three required binstubs for the consolidated success message while reporting shakapacker-watch separately as optional.

Create PR

Or push these changes by commenting:

@cursor push ba29c57533
Preview (ba29c57533)
diff --git a/lib/shakapacker/doctor.rb b/lib/shakapacker/doctor.rb
--- a/lib/shakapacker/doctor.rb
+++ b/lib/shakapacker/doctor.rb
@@ -1010,22 +1010,27 @@
           end
 
           def print_binstub_status
-            binstubs = [
+            required_binstubs = [
               "bin/shakapacker",
               "bin/shakapacker-dev-server",
-              "bin/shakapacker-config",
-              "bin/shakapacker-watch"
+              "bin/shakapacker-config"
             ]
+            optional_binstubs = ["bin/shakapacker-watch"]
 
-            existing_binstubs = binstubs.select { |b| doctor.root_path.join(b).exist? }
+            existing_required_binstubs = required_binstubs.select { |b| doctor.root_path.join(b).exist? }
+            existing_optional_binstubs = optional_binstubs.select { |b| doctor.root_path.join(b).exist? }
 
-            if existing_binstubs.length == binstubs.length
-              puts "✓ All Shakapacker binstubs found (#{existing_binstubs.join(', ')})"
-            elsif existing_binstubs.any?
-              existing_binstubs.each do |binstub|
+            if existing_required_binstubs.length == required_binstubs.length
+              puts "✓ All Shakapacker binstubs found (#{required_binstubs.join(', ')})"
+            elsif existing_required_binstubs.any?
+              existing_required_binstubs.each do |binstub|
                 puts "✓ #{binstub} found"
               end
             end
+
+            existing_optional_binstubs.each do |binstub|
+              puts "✓ #{binstub} found (optional)"
+            end
           end
 
           def print_info_messages

You can send follow-ups to this agent here.

Comment thread lib/shakapacker/doctor.rb
"bin/shakapacker-dev-server",
"bin/shakapacker-config"
"bin/shakapacker-config",
"bin/shakapacker-watch"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optional binstub prevents "All binstubs found" doctor message

Low Severity

Adding bin/shakapacker-watch to the binstubs array in print_binstub_status means the "✓ All Shakapacker binstubs found" message now requires this optional binstub to be present. Every existing installation that upgrades without running rake shakapacker:binstubs will regress from seeing the consolidated "All found" message to seeing individual per-binstub ✓ lines, even though the installation is perfectly functional. This contradicts the stated intent that shakapacker-watch is optional — the doctor now effectively treats it as required for a clean bill of health.

Fix in Cursor Fix in Web

@justin808 justin808 merged commit 4cd5250 into main Mar 29, 2026
57 checks passed
@justin808 justin808 deleted the jg/add-bin-shakapacker-watch branch March 29, 2026 03:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant