diff --git a/packages/gittensory-miner/lib/loop-cli.js b/packages/gittensory-miner/lib/loop-cli.js index cc8a28235..4186dccaa 100644 --- a/packages/gittensory-miner/lib/loop-cli.js +++ b/packages/gittensory-miner/lib/loop-cli.js @@ -238,10 +238,17 @@ export async function runLoop(args, options = {}) { const buildLoopClosureSummaryFn = options.buildLoopClosureSummary ?? buildLoopClosureSummary; const attemptLoopReentryFn = options.attemptLoopReentry ?? attemptLoopReentry; + // Resolved ONCE, at the CLI-entrypoint layer, mirroring manage-poll.js's own runManagePoll (its + // recordManagePollSnapshot callee has no env fallback of its own either -- the top-level CLI function is + // where `process.env.GITHUB_TOKEN` gets read, then threaded down explicitly to every real GitHub caller). + // pollPrDisposition (unlike runDiscover, which falls back to process.env.GITHUB_TOKEN internally) has NO + // such fallback -- an unresolved githubToken here would silently poll unauthenticated. + const githubToken = options.githubToken ?? env.GITHUB_TOKEN ?? ""; + async function runDiscoveryOnce() { await runDiscoverFn(discoverArgv(parsed), { initPortfolioQueue: () => portfolioQueue, - githubToken: options.githubToken, + githubToken, apiBaseUrl: options.apiBaseUrl, nowMs: nowMsFn(), }); @@ -377,7 +384,11 @@ export async function runLoop(args, options = {}) { if (submitted) { prNumber = parsePrNumberFromExecResult(lastResult?.execResult, claimed.repoFullName); if (prNumber !== null) { - prDisposition = await pollPrDispositionFn(claimed.repoFullName, prNumber, options.prDispositionOptions ?? {}); + prDisposition = await pollPrDispositionFn(claimed.repoFullName, prNumber, { + githubToken, + apiBaseUrl: options.apiBaseUrl, + ...(options.prDispositionOptions ?? {}), + }); if (prDisposition.state === "closed") { recordPrOutcomeSnapshotFn( { diff --git a/test/unit/miner-loop-cli.test.ts b/test/unit/miner-loop-cli.test.ts index 2df176330..6d71a345c 100644 --- a/test/unit/miner-loop-cli.test.ts +++ b/test/unit/miner-loop-cli.test.ts @@ -217,6 +217,7 @@ describe("runLoop (#5135)", () => { const pollPrDispositionSpy = vi.fn().mockResolvedValue({ state: "closed", merged: true, closedAt: "2026-07-12T00:00:00Z", attempts: 1 }); const exitCode = await runLoop(["acme/widgets", "--miner-login", "alice", "--max-cycles", "2", "--json"], { + env: { GITHUB_TOKEN: "ghp_loop_test" }, openGovernorState: () => governorState, initEventLedger: () => eventLedger, initGovernorLedger: () => governorLedger, @@ -233,7 +234,10 @@ describe("runLoop (#5135)", () => { const [attemptArgv] = runAttemptSpy.mock.calls[0]!; expect(attemptArgv).toEqual(["acme/widgets", "7", "--miner-login", "alice", "--base", "main"]); - expect(pollPrDispositionSpy).toHaveBeenCalledWith("acme/widgets", 123, expect.anything()); + // REGRESSION: the real githubToken (resolved from env.GITHUB_TOKEN, same as runDiscover's own call) must + // reach the poller -- an unauthenticated poll would silently hit GitHub's much lower rate limit or fail + // outright against a private repo. + expect(pollPrDispositionSpy).toHaveBeenCalledWith("acme/widgets", 123, expect.objectContaining({ githubToken: "ghp_loop_test" })); const after = reopenAfterRun(paths);