Context
packages/loopover-miner/lib/manage-status.js's runManageStatus (lines 213-241) opens three local SQLite
stores and then runs its actual work with only a try { ... } finally { ...close... } — there is no
catch clause at all:
export function runManageStatus(args = [], options = {}) {
const parsed = parseManageStatusArgs(args);
if ("error" in parsed) {
return reportCliFailure(argsWantJson(args), parsed.error);
}
const ownsPortfolioQueue = options.initPortfolioQueue === undefined;
const ownsEventLedger = options.initEventLedger === undefined;
const ownsRunStateStore = options.initRunStateStore === undefined;
const portfolioQueue = (options.initPortfolioQueue ?? initPortfolioQueueStore)();
const eventLedger = (options.initEventLedger ?? initEventLedger)();
const runStateStore = (options.initRunStateStore ?? initRunStateStore)();
try {
const rows = collectManageStatus({ portfolioQueue, eventLedger });
const runPortfolio = collectRunPortfolio({ portfolioQueue, eventLedger, runStateStore });
// ...console.log(...)...
return 0;
} finally {
if (ownsPortfolioQueue) portfolioQueue.close();
if (ownsEventLedger) eventLedger.close();
if (ownsRunStateStore) runStateStore.close();
}
}
collectManageStatus/collectRunPortfolio are not pure formatting helpers — they read real store data
(portfolioQueue.listQueue, eventLedger.readEvents, runStateStore.listRunStates) and throw on invalid
input (e.g. collectManageStatus throws invalid_portfolio_queue/invalid_event_ledger when a store
handle is malformed, and a corrupted/locked SQLite file surfaces as a thrown error from the underlying
node:sqlite calls). Because there is no catch, any such failure propagates out of runManageStatus as
an uncaught exception instead of going through this package's structured CLI failure path.
Every other IO-touching CLI command in this package wraps its store reads in try { ... } catch (error) { return reportCliFailure(...) }, confirmed by grepping lib/*-cli.js — attempt-cli.js, discover-cli.js,
loop-cli.js, portfolio-queue-cli.js, claim-ledger-cli.js, governor-ledger-cli.js,
governor-metrics-cli.js, event-ledger-cli.js, plan-store-cli.js, migrate-cli.js, and
orb-export.js's runOrbExportCli all catch and report via reportCliFailure(wantsJson, message, exitCode), which defaults exitCode to 2 (packages/loopover-miner/lib/cli-error.js). manage-status.js
is the one outlier — confirmed by grepping every lib/*-cli.js file for try { vs } catch counts, where
manage-status.js is the only file with try=1 catch=0.
The practical effect: loopover-miner manage status on a corrupted/locked local store crashes as an
unhandled exception (caught only by process-lifecycle.js's global uncaughtException handler, which logs
and exits with code 1) instead of the package's own structured failure output ({ ok: false, error } on
--json, or a clean stderr message) at exit code 2 — the same convention every sibling command already
follows.
Requirements
runManageStatus in packages/loopover-miner/lib/manage-status.js must wrap the collectManageStatus
/ collectRunPortfolio call site (and the subsequent console.log) in a try { ... } catch (error) { return reportCliFailure(parsed.json, describeCliError(error)); } block, matching the exact pattern
already used by runOrbExportCli (lib/orb-export.js) and runQueueList (lib/portfolio-queue-cli.js):
the store-opening lines stay outside the try, the catch sits between the existing try body and the
existing finally block (do not remove or reorder the finally's store-closing logic).
- Import
describeCliError alongside the existing argsWantJson, reportCliFailure import from
./cli-error.js (it is not currently imported in this file).
- Do not change
collectManageStatus/collectRunPortfolio's own throwing behavior, and do not add a
catch anywhere else in this file (e.g. parseManageStatusArgs has no IO and needs none).
- The fix is scoped to
packages/loopover-miner/lib/manage-status.js only.
Deliverables
Test Coverage Requirements
This repo's Codecov patch gate (codecov/patch) enforces target: 99%, threshold: 0%, branch-counted, on
every changed line/branch under src/**/packages/** — this fix is squarely inside packages/** and is
gated. Add the regression test described above (covering the new catch branch, both --json and
plain-text failure rendering) so the new branch is exercised, not just the existing happy path. Measure
locally with npm run test:coverage (unsharded), per this repo's coverage-measurement convention.
Expected Outcome
loopover-miner manage status on a failing/corrupted local store returns exit code 2 with a
reportCliFailure-shaped error (JSON { ok: false, error } under --json, plain stderr text otherwise),
identical in shape and exit-code convention to every other IO-touching subcommand in this package, instead of
crashing as an unhandled exception.
Links & Resources
packages/loopover-miner/lib/manage-status.js (the file to fix — runManageStatus, lines ~213-241)
packages/loopover-miner/lib/cli-error.js (reportCliFailure's default exitCode = 2, describeCliError)
packages/loopover-miner/lib/orb-export.js's runOrbExportCli and packages/loopover-miner/lib/portfolio-queue-cli.js's withPortfolioQueue-wrapped commands for the established try/catch/finally pattern to mirror
Context
packages/loopover-miner/lib/manage-status.js'srunManageStatus(lines 213-241) opens three local SQLitestores and then runs its actual work with only a
try { ... } finally { ...close... }— there is nocatchclause at all:collectManageStatus/collectRunPortfolioare not pure formatting helpers — they read real store data(
portfolioQueue.listQueue,eventLedger.readEvents,runStateStore.listRunStates) and throw on invalidinput (e.g.
collectManageStatusthrowsinvalid_portfolio_queue/invalid_event_ledgerwhen a storehandle is malformed, and a corrupted/locked SQLite file surfaces as a thrown error from the underlying
node:sqlitecalls). Because there is nocatch, any such failure propagates out ofrunManageStatusasan uncaught exception instead of going through this package's structured CLI failure path.
Every other IO-touching CLI command in this package wraps its store reads in
try { ... } catch (error) { return reportCliFailure(...) }, confirmed by greppinglib/*-cli.js—attempt-cli.js,discover-cli.js,loop-cli.js,portfolio-queue-cli.js,claim-ledger-cli.js,governor-ledger-cli.js,governor-metrics-cli.js,event-ledger-cli.js,plan-store-cli.js,migrate-cli.js, andorb-export.js'srunOrbExportCliall catch and report viareportCliFailure(wantsJson, message, exitCode), which defaultsexitCodeto2(packages/loopover-miner/lib/cli-error.js).manage-status.jsis the one outlier — confirmed by grepping every
lib/*-cli.jsfile fortry {vs} catchcounts, wheremanage-status.jsis the only file withtry=1 catch=0.The practical effect:
loopover-miner manage statuson a corrupted/locked local store crashes as anunhandled exception (caught only by
process-lifecycle.js's globaluncaughtExceptionhandler, which logsand exits with code
1) instead of the package's own structured failure output ({ ok: false, error }on--json, or a clean stderr message) at exit code2— the same convention every sibling command alreadyfollows.
Requirements
runManageStatusinpackages/loopover-miner/lib/manage-status.jsmust wrap thecollectManageStatus/
collectRunPortfoliocall site (and the subsequentconsole.log) in atry { ... } catch (error) { return reportCliFailure(parsed.json, describeCliError(error)); }block, matching the exact patternalready used by
runOrbExportCli(lib/orb-export.js) andrunQueueList(lib/portfolio-queue-cli.js):the store-opening lines stay outside the
try, thecatchsits between the existingtrybody and theexisting
finallyblock (do not remove or reorder thefinally's store-closing logic).describeCliErroralongside the existingargsWantJson, reportCliFailureimport from./cli-error.js(it is not currently imported in this file).collectManageStatus/collectRunPortfolio's own throwing behavior, and do not add acatchanywhere else in this file (e.g.parseManageStatusArgshas no IO and needs none).packages/loopover-miner/lib/manage-status.jsonly.Deliverables
runManageStatuscatches any error thrown while collecting/rendering manage status and reports it viareportCliFailure(parsed.json, describeCliError(error))(exit code2, matching every sibling CLIcommand), instead of letting it propagate as an unhandled exception.
finallyblock (closingportfolioQueue/eventLedger/runStateStorewhen owned) ispreserved unchanged.
initPortfolioQueue/initEventLedger/initRunStateStoresocollectManageStatusorcollectRunPortfoliothrows, and assertsrunManageStatusreturns2(not an uncaught exception) withthe expected
reportCliFailure-shaped output on both the--jsonand plain-text paths.Test Coverage Requirements
This repo's Codecov patch gate (
codecov/patch) enforcestarget: 99%, threshold: 0%, branch-counted, onevery changed line/branch under
src/**/packages/**— this fix is squarely insidepackages/**and isgated. Add the regression test described above (covering the new
catchbranch, both--jsonandplain-text failure rendering) so the new branch is exercised, not just the existing happy path. Measure
locally with
npm run test:coverage(unsharded), per this repo's coverage-measurement convention.Expected Outcome
loopover-miner manage statuson a failing/corrupted local store returns exit code2with areportCliFailure-shaped error (JSON{ ok: false, error }under--json, plain stderr text otherwise),identical in shape and exit-code convention to every other IO-touching subcommand in this package, instead of
crashing as an unhandled exception.
Links & Resources
packages/loopover-miner/lib/manage-status.js(the file to fix —runManageStatus, lines ~213-241)packages/loopover-miner/lib/cli-error.js(reportCliFailure's defaultexitCode = 2,describeCliError)packages/loopover-miner/lib/orb-export.js'srunOrbExportCliandpackages/loopover-miner/lib/portfolio-queue-cli.js'swithPortfolioQueue-wrapped commands for the established try/catch/finally pattern to mirror