feat(miner): add signal and crash handling to the CLI (#4826) - #5484
Conversation
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
Tip 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 ✅ Gittensory review result - approve/merge recommendedReview updated: 2026-07-12 23:14:30 UTC
✅ Suggested Action - Approve/Merge
Review summary Nits — 5 non-blocking
Review context
Contributor next steps
Signal definitions
[BETA] Chat with GittensoryAsk Gittensory 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://gittensory.aethereal.dev/docs/gittensory-commands 🟩 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 Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5484 +/- ##
=======================================
Coverage 94.74% 94.74%
=======================================
Files 563 564 +1
Lines 44823 44869 +46
Branches 14669 14669
=======================================
+ Hits 42467 42513 +46
Misses 1621 1621
Partials 735 735
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Closes #4826.
Summary
The miner CLI had no process-level signal handling anywhere — no
SIGINT/SIGTERM/uncaughtException/unhandledRejectionhandlers. The entrypoint dispatches through a chain of bareprocess.exit()calls with no cleanup hook, so an interrupted run (Ctrl-C,systemctl stop, or an uncaught error) could die mid-write and leave whatever local SQLite ledger it was touching in an undefined state.This adds a single cleanup chokepoint and wires it in once at CLI startup, covering every subcommand — without touching any command's business logic (cleanup only, per the issue boundary).
What changed
packages/gittensory-miner/lib/process-lifecycle.js— the crash-safety module:registerCleanupResource(resource)/closeAllCleanupResources()— an ordered registry of closable resources (a{ close() }store or a plain function). Returns an idempotent unregister handle; each close is individually try/caught so one failing close can't strand the others.installCliSignalHandlers(options)— installs handlers once (idempotent;forcefor tests). OnSIGINT/SIGTERM: close all resources, then exit with the conventional128 + signalcode (130 / 143). OnuncaughtException/unhandledRejection: log the error and exit non-zero (1) instead of crashing silently. Every dependency (process,log,exit) is injectable, so the handlers are fully unit-testable without signalling the test runner.process-lifecycle.d.tstypes and test-only helpers (cleanupResourceCount,resetProcessLifecycleForTesting).lib/local-store.js—openLocalStoreDbnow auto-registers every opened store with the cleanup registry and wrapsclose()to unregister first. This is the DRY chokepoint every local ledger (run-state, claim, portfolio-queue, event) already funnels through, so every ledger is covered automatically. The happy path never double-closes, and a long-runningloopnever accumulates stale handles.bin/gittensory-miner.js— callsinstallCliSignalHandlers()once at the top, before any dispatch, so all subcommands (including the localstatus/doctor/metricsfast paths) are covered.package.json— addedlib/process-lifecycle.jsto thebuildnode --checklist.README.md— documented the crash-safety behavior in the "Local storage" section.Acceptance criteria
bin/gittensory-miner.jsbefore dispatch; idempotent.local-store.jsis registered and closed on exit; unit-tested against realnode:sqlitestores.SIGINT/SIGTERMmid-run leaves every ledger valid/non-corrupt — handler closes all open stores (flushing SQLite) before exiting 130/143.uncaughtException/unhandledRejectionhandlers log the stack/reason andexit(1).Tests
test/unit/miner-process-lifecycle.test.ts— full-branch coverage of the registry, each signal/error handler (via a fakeprocesswhose listeners are invoked directly), default vs. injectedlog/exit, exit codes 130/143/1,Error-with-stack /Error-without-stack / non-Errorreason rendering, idempotency +force, cleanup-error reporting, and the default-real-processbranch (listeners cleaned up afterward).test/unit/miner-local-store.test.ts— a store opened viaopenLocalStoreDbis registered, unregisters on normalclose(), and is actually closed bycloseAllCleanupResources()at crash time (real:memory:node:sqliteDB).Scope / boundaries
bin/**is not in the Codecovincludeglobs (onlypackages/gittensory-miner/lib/**/*.js), so the one-line entrypoint wiring carries no patch-coverage burden; the measured logic lives inprocess-lifecycle.js+ thelocal-store.jschange, both fully unit-tested.Verification notes
node --checkpasses for all changed JS; no linter errors.process-lifecycle.jsis pure JS (nonode:sqlite), I ran the real compiled module through a standalone script exercising every registry/handler/branch — all green — in addition to the Vitest suite.npm run test:ci+npm run test:coverageunder Node 22 before pushing (the Vitest gate andnode:sqlite-backedlocal-storetests can't run in this Node 18 sandbox) — the loopover gate is one-shot auto-merge/close.