Repository navigation
fix(watch): implement the documented stop signals (--sentinel-file and .squad/ralph-stop) - #1719
Conversation
`--sentinel-file` was parsed and threaded into watch config but never read, and `.squad/ralph-stop` appeared only in documentation with no code references. A running watch loop could therefore only be stopped by sending a signal to the process. Add `detectStopSignal()` and check it at the top of every round: - `--sentinel-file <path>` stops the run once that file is removed, matching the existing doc comment in watch/config.ts. The file is created at startup if absent so the first round does not stop immediately. - `<squadDir>/ralph-stop` stops the run once it exists, matching the documented `touch .squad/ralph-stop` in the README. Both reuse the existing graceful shutdown path, so PID cleanup, circuit breaker persistence and session teardown all still run. The shutdown closure was hoisted out of the interval promise so a stop detected on the very first round can shut down cleanly. Closes github#1711. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🟡 Impact Analysis — PR #1719Risk tier: 🟡 MEDIUM 📊 Summary
🎯 Risk Factors
📦 Modules Affectedroot (1 file)
squad-cli (1 file)
tests (1 file)
|
🛫 PR Readiness Check
PR Scope: 📦🔧 Mixed (product + infrastructure)
|
| Status | Check | Details |
|---|---|---|
| ❌ | Single commit | 2 commits — consider squashing before review |
| ✅ | Not in draft | Ready for review |
| ❌ | Branch up to date | dev is 84 commit(s) ahead — rebase recommended |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | Changeset file found |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ✅ | Copilot threads resolved | 0 active Copilot thread(s) resolved (1 outdated skipped) |
| ❌ | CI passing | 6 check(s) still running |
Files Changed (3 files, +207 −20)
| File | +/− |
|---|---|
.changeset/watch-stop-signal.md |
+5 −0 |
packages/squad-cli/src/cli/commands/watch/index.ts |
+109 −20 |
test/watch-stop-signal.test.ts |
+93 −0 |
Total: +207 −20
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
|
@swigerb heads up — this one looks finished from the outside and is currently the only thing standing between us and an empty PR queue. Status as of now:
If it's actually done, could you flip it out of draft? Happy to merge it the moment you do. If something's still outstanding on your end, drop a note here and we'll work it — just don't want it sitting idle on a technicality. Context: we cleared 16 of 19 open PRs today; #1750 is the only other one still in flight. |
There was a problem hiding this comment.
🟢 Approval recommended
The implementation aligns with the documented semantics and includes targeted unit tests; only a small misleading doc comment needs cleanup.
Pull request overview
Implements the two documented external “stop signals” for squad watch so a running watch loop can be stopped gracefully without sending a process signal (Closes #1711).
Changes:
- Add
detectStopSignal()and check it at the start of each watch round, supporting both--sentinel-file <path>(stop when deleted) and<squadDir>/ralph-stop(stop when created). - Hoist
shutdown()so a stop detected on the first round can still exit cleanly and resolve the run promise. - Add a focused unit test suite for stop-signal behavior and a patch changeset for
@bradygaster/squad-cli.
File summaries
| File | Description |
|---|---|
packages/squad-cli/src/cli/commands/watch/index.ts |
Implements stop-signal detection, startup banner hints, and integrates graceful stop into the watch loop. |
test/watch-stop-signal.test.ts |
Adds unit tests for detectStopSignal() covering precedence and path-resolution behaviors. |
.changeset/watch-stop-signal.md |
Declares a patch release for the CLI with a user-facing description of the new behavior. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The constant names the ralph-stop *stop file*, not the user-configured sentinel file. Calling it a sentinel conflated the two mechanisms, which is confusing now that both exist with opposite trigger semantics. Refs github#1711
Closes #1711.
Problem
squad watchdocuments two ways to stop a running loop, and neither was implemented. The only way to stop a run was to signal the process.--sentinel-file <path>was parsed atcli-entry.ts:641-643, passed through atcli-entry.ts:704, and merged inwatch/config.ts:103,144,155- with a doc comment reading "Path to a sentinel file - watch shuts down gracefully when removed." Nothing ever read the value..squad/ralph-stopappeared inREADME.md:277and four places indocs/features/watch-next-gen.md, but the string had zero references anywhere inpackages/.Change
Adds
detectStopSignal()and calls it at the top of every round, before any other work:--sentinel-file <path>stops the run once that file is removed, matching the existing doc comment. The file is created at startup if it does not already exist, otherwise the very first round would stop immediately.<squadDir>/ralph-stopstops the run once that file exists, matching the documentedtouch .squad/ralph-stop.Both paths log the reason and then reuse the existing
shutdown(), so PID cleanup, circuit-breaker persistence, event emission and log-stream teardown all still happen. Both stop targets are printed in the startup banner so they are discoverable.The
shutdownclosure was hoisted out of the interval promise so that a stop signal detected on the first round can shut down cleanly - previously it was defined inside the promise executor and could not be reached before the interval was established. The promise resolves immediately in that case.detectStopSignal()takes an injectedexistsprobe, which keeps it pure and directly unit-testable.Tests
New
test/watch-stop-signal.test.ts, 8 cases:ralph-stopexistsralph-stopstill fires while the sentinel is presentValidation
npm run lint(tsc --noEmit, both packages) - cleannpx vitest run test/watch-stop-signal.test.ts test/watch-rate-limit.test.ts test/watch-cleanup.test.ts- 28 passedI was unable to complete a full
npm testrun locally due to a package-proxy restriction on my machine, not a failure in the suite. Worth a CI confirmation on the full suite.Notes
Behaviour is backwards compatible - with no
--sentinel-fileand noralph-stoppresent,detectStopSignal()returnsnulland the loop behaves exactly as before.Changeset included (
patch,@bradygaster/squad-cli).