From de5f29d66b1ff0e121ea52bdf88a0046b0befb12 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:41:33 -0700 Subject: [PATCH] Extract check rerun error classification with operation context Create classifyRerunChecksError() to provide operation-specific error messages when check reruns fail. This replaces generic GitHub error copy with context appropriate to what the user attempted (rerun checks). Follows the pattern of classifyListPrsError and improves error handling by delegating extraction to extractExecError. --- .../github/client/check/rerun-pr-checks.ts | 16 ++++++---------- src/main/github/gh-error-classification.ts | 18 ++++++++++++++++++ .../updater.linux-root-package-install.test.ts | 2 ++ 3 files changed, 26 insertions(+), 10 deletions(-) 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()