Preserve checks cache on refresh failures

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-05-14 02:50:04 -04:00
co-authored by Orca
parent 581cfee01b
commit dcebb81503
5 changed files with 78 additions and 3 deletions
+9
View File
@@ -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')
})
})
+1 -1
View File
@@ -1232,7 +1232,7 @@ export async function getPRChecks(
}
} catch (err) {
console.warn('getPRChecks failed:', err)
return []
throw err
}
}
+7 -1
View File
@@ -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 {
@@ -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<AppState>)
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<AppState>)
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', () => {
+5 -1
View File
@@ -1430,7 +1430,11 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (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)
}