feat(cli): clean exit 130 on Ctrl+C outside a run (#241, PR 2 of 2)#256
Conversation
|
Warning Review limit reached
Next review available in: 39 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughThe CLI now returns exit code 130 for ChangesInterrupt exit contract
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
A KeyboardInterrupt that escapes main() outside engine.run() (config load, engine construction, a handler with no run loop) was uncaught. CPython already ended the process at 130 by re-raising SIGINT — but as death-by- signal with a bare traceback dumped after any partial --json stdout. Catch it at the dispatch boundary, print a one-line `interrupted` to stderr, and return the new ExitCode.INTERRUPTED=130 (128 + SIGINT). The exit code is unchanged (130); what changes is the clean message, empty stdout, and a graceful exit(130) in place of signal death (a Python caller reading subprocess.returncode sees 130 rather than -2). The in-run interrupt path (engine.run()'s own clean RunStopped) is untouched: a Ctrl+C during a run still finalizes as a resumable stopped run at rc 0. Released-contract clarification, PR 2 of 2 for the exit-code taxonomy (#241); ships alone and revertibly.
6670d2e to
9415520
Compare
|
@CodeRabbit review |
✅ Action performedReview finished.
|
|
@CodeRabbit review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/bmad_loop/cli.py`:
- Line 92: Replace the en dash in the numeric range `3–129` with a hyphen in the
docstrings at src/bmad_loop/cli.py lines 92-92 and tests/test_entry_point.py
lines 153-153; no other changes are needed.
In `@tests/test_entry_point.py`:
- Around line 122-137: Update test_exit_keyboard_interrupt_is_130 to invoke
cli.main with the validate --json option, then assert captured.err exactly
equals "interrupted\n" rather than stripping whitespace. Keep the existing
exit-code, empty-stdout, and traceback assertions unchanged.
🪄 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: 353ad3ae-91d9-4bbb-91ce-612702624fc4
📒 Files selected for processing (4)
CHANGELOG.mdREADME.mdsrc/bmad_loop/cli.pytests/test_entry_point.py
…I hyphen (#241) Address CodeRabbit review on PR #256: - test_exit_keyboard_interrupt_is_130 now invokes `validate --json` — the issue's contract is "empty stdout under --json rules", so name the machine surface it protects (a --json command must emit no partial document on interrupt). Tighten the stderr check to an exact `== "interrupted\n"` instead of .strip(), pinning one line with no stray whitespace. - Swap the en dash in the `3-129` exit-code range for an ASCII hyphen in the two Python docstrings (RUF002). The repo's ruff has no `select` so this is not enforced by trunk check, but the fix is a cost-free character swap with no suppression comment.
Closes #241 (PR 2 of 2 — the
ExitCodeenum + docs landed in #255).What
A
KeyboardInterruptthat escapesmain()outsideengine.run()— during config load, engine construction, or a handler with no run loop — was uncaught. AKeyboardInterruptis aBaseException, so the broadexcept Exceptionbackstop never caught it.This catches it at the dispatch boundary, prints a one-line
interruptedto stderr, and returns the newExitCode.INTERRUPTED = 130(128 + SIGINT).The exit code was already 130 — this makes it clean
The issue text (and the original F-5 assessment) said this path exited rc 1. That is wrong, and PR #255's review already corrected the record: uncaught, CPython re-raises SIGINT and the process already ended at 130 — verified again here:
So the exit code is unchanged. What changes:
$?130130subprocess.returncode-2(signal death)130(clean exit)interrupted--jsoncommand had already writtenThe clean, empty stdout matters most for
--jsonconsumers, which otherwise had a half-written document followed by a traceback.Deliberately unchanged
The in-run interrupt path is
engine.run()'s own cleanRunStopped(engine.py:333-348) and never reaches this handler — a Ctrl+C during a run still finalizes as a resumablestoppedrun at rc 0. Per finding F-5 that path's rc must not move, soengine.pyis untouched.Tests
KeyboardInterruptfrom a monkeypatched handler → rc130, stderrinterrupted, empty stdout (--json-safe), no traceback.ExitCodeenum tests updated for the new member ({0, 1, 2, 130}).-2→130subprocess.returncodeshift; the README exit-code table gains theINTERRUPTEDrow (the feat(cli): ExitCode enum + exit-code docs (#241, PR 1 of 2) #255 note that forward-referenced this PR is replaced).test_module_skills_syncfailures are pre-existing local.agents/.claudemodule.yamldrift (untracked install artifacts), not from this diff.trunk checkclean.Summary by CodeRabbit
Bug Fixes
interruptedmessage instead of a traceback.Documentation