diff --git a/src/main/github/client/check/rerun-pr-checks.ts b/src/main/github/client/check/rerun-pr-checks.ts index f0d66f29a17..82059bab664 100644 --- a/src/main/github/client/check/rerun-pr-checks.ts +++ b/src/main/github/client/check/rerun-pr-checks.ts @@ -1,11 +1,8 @@ import type { GitHubRerunPRChecksResult } from '../../../../shared/github/check-types' -import { - ghExecFileAsync, - acquire, - release, - classifyGhError, - type LocalGitExecOptions -} from '../../gh-utils' +import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' +// Why: pure error helpers come from their own modules so tests that mock gh-utils still classify for real. +import { extractExecError } from '../../../git/exec-error' +import { classifyRerunChecksError } from '../../gh-error-classification' import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' import { getPRChecks } from './get-pr-checks' import { parseActionsRunId } from './check-detail-field-mapping' @@ -92,9 +89,8 @@ export async function rerunPRChecks( } return { ok: true, count } } catch (err) { - const message = - err instanceof Error ? err.message : typeof err === 'string' ? err : 'Unknown error' - const classified = classifyGhError(message).message + const { stderr } = extractExecError(err) + const classified = classifyRerunChecksError(stderr).message // Why: these POSTs are not idempotent — say how many reruns already started so a retry isn't blind. return { ok: false, diff --git a/src/main/github/gh-error-classification.ts b/src/main/github/gh-error-classification.ts index 70905162f27..1093b88dc68 100644 --- a/src/main/github/gh-error-classification.ts +++ b/src/main/github/gh-error-classification.ts @@ -81,3 +81,21 @@ export function classifyListPrsError(stderr: string): ClassifiedError { const c = classifyGhError(stderr) return { type: c.type, message: `Failed to load pull requests: ${stderr.trim()}` } } + +// Why: classifyGhError's copy names an "issue"; a failed check rerun must name +// the operation the user actually triggered. +export function classifyRerunChecksError(stderr: string): ClassifiedError { + const c = classifyGhError(stderr) + const trimmed = stderr.trim() + const rerunMessages: Record = { + permission_denied: + "You don't have permission to rerun checks on this repository. Check your GitHub token scopes.", + not_found: 'Check run not found — it may have expired or been deleted.', + issues_disabled: `Failed to rerun checks: ${trimmed}`, + validation_error: `Could not rerun checks — ${trimmed}`, + rate_limited: c.message, + network_error: c.message, + unknown: `Failed to rerun checks: ${trimmed}` + } + return { type: c.type, message: rerunMessages[c.type] } +} diff --git a/src/main/updater.linux-root-package-install.test.ts b/src/main/updater.linux-root-package-install.test.ts index 31414745967..13534866863 100644 --- a/src/main/updater.linux-root-package-install.test.ts +++ b/src/main/updater.linux-root-package-install.test.ts @@ -601,6 +601,8 @@ describe('updater', () => { await vi.advanceTimersByTimeAsync(100) expect(revalidation.invocationCount()).toBe(1) updater.quitAndInstall() + // Advancing here proves the second request never scheduled its own quit timer. + await vi.advanceTimersByTimeAsync(100) expect(revalidation.invocationCount()).toBe(1) revalidation.settle({ ok: true }) await settleQuitAndInstall()