From d85926d82a9be5952107e57e2b3679c916e9a08c Mon Sep 17 00:00:00 2001 From: dhgoal <153369624+dhgoal@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:35:15 +0200 Subject: [PATCH] fix(miner): exit 2, not 1, on a secret-mount failure bin/loopover-miner.js's loadMinerFileSecrets catch exited 1, but docs/unattended-scheduling.md's contract only defines 0 (success) and 2 (failure -- "Alert on this"). An operator wiring alerting strictly to exit code 2, as that doc instructs, silently missed a broken secret mount (a bad GITHUB_TOKEN_FILE or similar). Checked every other exit in this file: the secret-mount catch was the only hardcoded exit 1: the rest pass a subcommand's own resolved code or 0. lib/env-file-indirection.js is untouched -- the throw is correct, only the CLI-boundary translation was wrong. The existing end-to-end test pinned status 1, so it is updated to assert the documented 2 (it spawns the real bin against a missing GITHUB_TOKEN_FILE). Closes #6162 --- packages/loopover-miner/bin/loopover-miner.js | 4 +++- test/unit/miner-env-file-indirection.test.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/loopover-miner/bin/loopover-miner.js b/packages/loopover-miner/bin/loopover-miner.js index 110f131883..cb53779d51 100755 --- a/packages/loopover-miner/bin/loopover-miner.js +++ b/packages/loopover-miner/bin/loopover-miner.js @@ -38,11 +38,13 @@ import { resolveMinerVersion } from "../lib/version.js"; // deeper in the call graph) reads plain env vars, so this single early pass is all that's needed for the whole // CLI (#5178). A broken secret mount fails the process fast and loud with a clear message, instead of an // uncaught-exception stack trace or a silent empty credential surfacing as a confusing GitHub 401 later. +// Exits 2, not 1: docs/unattended-scheduling.md's contract only defines 0 (success) and 2 (failure -- "Alert +// on this"), so an operator alerting strictly on 2 would otherwise miss a broken secret mount entirely. try { loadMinerFileSecrets(); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); - process.exit(1); + process.exit(2); } // Opt-in Sentry (#6011): a complete no-op unless the operator sets LOOPOVER_MINER_SENTRY_DSN themselves. Must diff --git a/test/unit/miner-env-file-indirection.test.ts b/test/unit/miner-env-file-indirection.test.ts index 1a31c40956..5c733a75a2 100644 --- a/test/unit/miner-env-file-indirection.test.ts +++ b/test/unit/miner-env-file-indirection.test.ts @@ -149,7 +149,10 @@ describe("loadMinerFileSecrets (#5178)", () => { expect(result.stderr).not.toContain("ghp_end_to_end_value"); }); - it("fails the process fast with a clear error when GITHUB_TOKEN_FILE points at a missing file", () => { + // #6162: exit code 2, not 1 -- docs/unattended-scheduling.md's contract only defines 0 (success) and 2 + // (failure -- "Alert on this"), so an operator wiring alerting strictly to 2 must catch a broken secret + // mount like this one. + it("fails the process fast with a clear error and the documented failure exit code when GITHUB_TOKEN_FILE points at a missing file", () => { const result = spawnSync("node", [bin, "status"], { encoding: "utf8", env: { @@ -159,7 +162,8 @@ describe("loadMinerFileSecrets (#5178)", () => { }, }); - expect(result.status).toBe(1); + expect(result.status).toBe(2); + expect(result.status).not.toBe(1); expect(result.stderr).toContain("GITHUB_TOKEN_FILE"); expect(result.stderr).toContain("/definitely/does/not/exist/github_token"); });