diff --git a/src/main/github/client-pr-checks.test.ts b/src/main/github/client-pr-checks.test.ts index 49064da51e3..bd0804c69c3 100644 --- a/src/main/github/client-pr-checks.test.ts +++ b/src/main/github/client-pr-checks.test.ts @@ -103,4 +103,13 @@ describe('getPRChecks', () => { } ]) }) + + it('throws when both check-runs and gh pr checks fail', async () => { + getOwnerRepoMock.mockResolvedValueOnce({ owner: 'acme', repo: 'widgets' }) + ghExecFileAsyncMock + .mockRejectedValueOnce(new Error('gh: No commit found for SHA: stale-head (HTTP 422)')) + .mockRejectedValueOnce(new Error('rate limited')) + + await expect(getPRChecks('/repo-root', 42, 'stale-head')).rejects.toThrow('rate limited') + }) }) diff --git a/src/main/github/client.ts b/src/main/github/client.ts index a86d2e56121..35671309dfd 100644 --- a/src/main/github/client.ts +++ b/src/main/github/client.ts @@ -1232,7 +1232,7 @@ export async function getPRChecks( } } catch (err) { console.warn('getPRChecks failed:', err) - return [] + throw err } } diff --git a/src/main/github/work-item-details.ts b/src/main/github/work-item-details.ts index a0ea1983d79..eb30d8c0ba0 100644 --- a/src/main/github/work-item-details.ts +++ b/src/main/github/work-item-details.ts @@ -579,9 +579,15 @@ export async function getWorkItemDetails( // Promise.all above, so there's no ordering requirement between them. const [mentionParticipants, checks] = await Promise.all([ getMentionParticipants(repoPath, item, comments, participants), - shas?.headSha + (shas?.headSha ? getPRChecks(repoPath, item.number, shas.headSha) : getPRChecks(repoPath, item.number) + ).catch((err) => { + // Why: details can still render body/comments/files when check fetching + // is rate-limited or temporarily unavailable. + console.warn('Failed to fetch PR checks for work item details:', err) + return [] + }) ]) return { diff --git a/src/renderer/src/store/slices/github.test.ts b/src/renderer/src/store/slices/github.test.ts index 37039819815..6e7820c6034 100644 --- a/src/renderer/src/store/slices/github.test.ts +++ b/src/renderer/src/store/slices/github.test.ts @@ -227,6 +227,62 @@ describe('createGitHubSlice.fetchPRChecks', () => { noCache: true }) }) + + it('preserves cached checks when the checks IPC fails', async () => { + const store = createTestStore() + const repoPath = '/repo' + const branch = 'feature/test' + const checksCacheKey = `${repoPath}::pr-checks::12` + const cachedChecks = [ + { name: 'build', status: 'completed', conclusion: 'failure', url: null } as const + ] + + store.setState({ + checksCache: { + [checksCacheKey]: { + data: cachedChecks, + fetchedAt: 1, + headSha: 'abc123head' + } + } + } as unknown as Partial) + mockApi.gh.prChecks.mockRejectedValueOnce(new Error('rate limited')) + + await expect( + store.getState().fetchPRChecks(repoPath, 12, branch, 'abc123head', { force: true }) + ).resolves.toEqual(cachedChecks) + + expect(store.getState().checksCache[checksCacheKey]?.data).toEqual(cachedChecks) + expect(store.getState().checksCache[checksCacheKey]?.fetchedAt).toBe(1) + }) + + it('does not return cached checks for a different requested head SHA after IPC failure', async () => { + const store = createTestStore() + const repoPath = '/repo' + const branch = 'feature/test' + const checksCacheKey = `${repoPath}::pr-checks::12` + const oldHeadChecks = [ + { name: 'build', status: 'completed', conclusion: 'success', url: null } as const + ] + + store.setState({ + checksCache: { + [checksCacheKey]: { + data: oldHeadChecks, + fetchedAt: 1, + headSha: 'old-head' + } + } + } as unknown as Partial) + mockApi.gh.prChecks.mockRejectedValueOnce(new Error('rate limited')) + + await expect( + store.getState().fetchPRChecks(repoPath, 12, branch, 'new-head', { force: true }) + ).resolves.toEqual([]) + + expect(store.getState().checksCache[checksCacheKey]?.data).toEqual(oldHeadChecks) + expect(store.getState().checksCache[checksCacheKey]?.headSha).toBe('old-head') + }) }) describe('createGitHubSlice.fetchPRForBranch', () => { diff --git a/src/renderer/src/store/slices/github.ts b/src/renderer/src/store/slices/github.ts index 72f0c31e580..b66bc6b98c2 100644 --- a/src/renderer/src/store/slices/github.ts +++ b/src/renderer/src/store/slices/github.ts @@ -1430,7 +1430,11 @@ export const createGitHubSlice: StateCreator = (s return checks } catch (err) { console.error('Failed to fetch PR checks:', err) - return get().checksCache[cacheKey]?.data ?? [] + const latestCached = get().checksCache[cacheKey] + if (latestCached?.data && (!headSha || latestCached.headSha === headSha)) { + return latestCached.data + } + return [] } finally { inflightChecksRequests.delete(inflightKey) }