fix(miner): a rejecting captureError must not skip runCleanup()/exit in the crash handlers (#9688) - #9902
Conversation
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-29 20:37:03 UTC
Review summary Nits — 4 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
…in the crash handlers (JSONbored#9688) process-lifecycle.ts calls itself "the single cleanup chokepoint", but its uncaughtException/unhandledRejection handlers `await captureError(...)` with no guard. The "never throws/rejects" contract on the injectable captureError option lived only in prose -- if an injected hook rejects (or throws synchronously), runCleanup() and exit(1) are both skipped: every registered SQLite handle stays open and unflushed, and on unhandledRejection the handler's own rejected promise re-enters the same handler. - Wrap the `await captureError(...)` in each handler in try/catch, logging a distinct `error capture failed: ...` message via the same injected log and existing describeError helper, so runCleanup() + exit(1) always run in their current order. - Update the captureError doc comment to document the catch-and-log behaviour instead of claiming it is "Never expected to throw/reject". - SIGINT/SIGTERM handlers and the production bin wiring are untouched. Three named regression tests: a rejecting captureError on each handler, and a synchronously-throwing one, each asserting the registered store's close() ran and exit received 1. All three fail against the current code. Closes JSONbored#9688 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9902 +/- ##
==========================================
+ Coverage 79.29% 79.31% +0.01%
==========================================
Files 281 282 +1
Lines 58566 58616 +50
Branches 6785 6791 +6
==========================================
+ Hits 46440 46490 +50
Misses 11843 11843
Partials 283 283
Flags with carried forward coverage won't be shown. Click here to find out more.
|
What
process-lifecycle.tscalls itself "the single cleanup chokepoint", yet itsuncaughtException/unhandledRejectionhandlersawait captureError(...)with no guard. The "never throws/rejects" contract on the public injectablecaptureErroroption existed only as prose. If an injected hook rejects — or throws synchronously —runCleanup()andexit(1)are both skipped: every registered SQLite handle stays open and unflushed (the exact failure this module exists to prevent), and onunhandledRejectionthe handler's own rejected promise re-enters the same handler.How
await captureError(...)in both handlers intry/catch, logging a distinctloopover-miner: error capture failed: ...message via the same injectedlogand the module's existingdescribeErrorhelper, sorunCleanup()+exit(1)always run in their current order and exactly once.captureErrordoc comment to document the catch-and-log behaviour instead of claiming it is "Never expected to throw/reject".SIGINT/SIGTERMhandlers and the productionbin/loopover-miner.tswiring are untouched.Tests
Three named regression tests in
test/unit/miner-process-lifecycle.test.ts, each failing against the current code:captureErroronuncaughtException— the registered store'sclose()still ran,exit(1)fired, and the failure was logged.unhandledRejection.captureErrorthat throws synchronously is also caught.These cover the new
catcharm of both handlers plus the synchronous-throw path; the resolve arm is already covered by the existing#6011tests.Closes #9688