From e0e00e73d8ff1488c53e2a0190e444a9039e361e Mon Sep 17 00:00:00 2001 From: reyanthony062001-ops Date: Wed, 15 Jul 2026 07:47:03 +0000 Subject: [PATCH] fix(miner): honor --json on the discover --dry-run failure path discover's --dry-run catch block wrote a plain-text line to stderr and returned 2 regardless of --json, unlike the non-dry-run catch 20 lines later (and the second failure path further down) which both route through reportCliFailure(parsed.json, describeCliError(error)). A caller scripting against `loopover-miner discover ... --dry-run --json` got unparseable plain text instead of the {ok:false,error} object every other failure path in this CLI emits. Route the dry-run catch through the same shared helper. describeCliError is byte-identical to the previous inline expression, so the non-json path (stderr text, exit 2) is unchanged; adds a regression test asserting the --dry-run --json failure prints a parseable {ok:false,error} object. Closes #5830 --- packages/loopover-miner/lib/discover-cli.js | 3 +-- test/unit/miner-discover-cli.test.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/loopover-miner/lib/discover-cli.js b/packages/loopover-miner/lib/discover-cli.js index 333de85c0d..25fd5c5226 100644 --- a/packages/loopover-miner/lib/discover-cli.js +++ b/packages/loopover-miner/lib/discover-cli.js @@ -200,8 +200,7 @@ export async function runDiscover(args, options = {}) { } return 0; } catch (error) { - console.error(error instanceof Error ? error.message : String(error)); - return 2; + return reportCliFailure(parsed.json, describeCliError(error)); } } diff --git a/test/unit/miner-discover-cli.test.ts b/test/unit/miner-discover-cli.test.ts index ae0d48f0d3..7c223b672b 100644 --- a/test/unit/miner-discover-cli.test.ts +++ b/test/unit/miner-discover-cli.test.ts @@ -427,6 +427,25 @@ describe("runDiscover (#4247)", () => { expect(error).toHaveBeenCalledWith("github_unreachable"); }); + it("#5830: --dry-run --json reports fan-out failures as a parseable {ok:false,error} object", async () => { + const initPortfolioQueue = vi.fn(); + const fetchCandidateIssuesWithSummary = vi.fn(async () => { + throw new Error("github_unreachable"); + }); + const log = vi.spyOn(console, "log").mockImplementation(() => undefined); + + const exitCode = await runDiscover(["acme/widgets", "--dry-run", "--json"], { + nowMs: NOW, + initPortfolioQueue, + fetchCandidateIssuesWithSummary, + }); + + expect(exitCode).toBe(2); + expect(initPortfolioQueue).not.toHaveBeenCalled(); + const payload = JSON.parse(String(log.mock.calls[0]?.[0])); + expect(payload).toEqual({ ok: false, error: "github_unreachable" }); + }); + it("#4847: --dry-run stringifies a thrown non-Error value instead of crashing", async () => { const fetchCandidateIssuesWithSummary = vi.fn(async () => { throw "raw_string_fault";