diff --git a/src/renderer/src/store/slices/github.test.ts b/src/renderer/src/store/slices/github.test.ts index d5c1a4bc507..dd4453c09ec 100644 --- a/src/renderer/src/store/slices/github.test.ts +++ b/src/renderer/src/store/slices/github.test.ts @@ -6322,6 +6322,71 @@ describe('createGitHubSlice.fetchWorkItems source/error envelope', () => { } }) + it('flags a GitHub outage returned by a remote runtime method', async () => { + const store = createTestStore() + const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + runtimeEnvironmentCall.mockResolvedValueOnce({ + id: 'rpc-work-items-outage', + ok: false, + error: { code: 'runtime_error', message: 'HTTP 503: Service Unavailable' }, + _meta: { runtimeId: 'remote-runtime' } + }) + store.setState({ + settings: { activeRuntimeEnvironmentId: 'env-1' }, + repos: [{ id: 'runtime-repo-id', path: '/server/repo', name: 'repo', kind: 'git' }] + } as unknown as Partial) + + try { + const result = await store + .getState() + .fetchWorkItemsAcrossRepos( + [{ repoId: 'caller-repo-id', path: '/server/repo' }], + 24, + 100, + '' + ) + + expect(result.githubUnavailable).toBe(true) + expect(result.failedCount).toBe(1) + } finally { + consoleWarn.mockRestore() + } + }) + + it('does not attribute a remote runtime transport timeout to GitHub', async () => { + const store = createTestStore() + const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + runtimeEnvironmentCall.mockResolvedValueOnce({ + id: 'rpc-work-items-runtime-timeout', + ok: false, + error: { + code: 'runtime_unavailable', + message: 'Runtime request timed out before github.listWorkItems completed' + }, + _meta: { runtimeId: null } + }) + store.setState({ + settings: { activeRuntimeEnvironmentId: 'env-1' }, + repos: [{ id: 'runtime-repo-id', path: '/server/repo', name: 'repo', kind: 'git' }] + } as unknown as Partial) + + try { + const result = await store + .getState() + .fetchWorkItemsAcrossRepos( + [{ repoId: 'caller-repo-id', path: '/server/repo' }], + 24, + 100, + '' + ) + + expect(result.githubUnavailable).toBe(false) + expect(result.failedCount).toBe(1) + } finally { + consoleWarn.mockRestore() + } + }) + it('flags githubUnavailable while serving stale cached rows after a failed refresh', async () => { const store = createTestStore() const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}) diff --git a/src/renderer/src/store/slices/github.ts b/src/renderer/src/store/slices/github.ts index fd9351dd5ee..322fc8568c0 100644 --- a/src/renderer/src/store/slices/github.ts +++ b/src/renderer/src/store/slices/github.ts @@ -39,7 +39,11 @@ import { PER_REPO_FETCH_LIMIT } from '../../../../shared/work-items' import { deriveCheckStatusFromChecks, syncPRChecksStatus } from './github-checks' -import { callRuntimeRpc, getActiveRuntimeTarget } from '../../runtime/runtime-rpc-client' +import { + callRuntimeRpc, + getActiveRuntimeTarget, + RuntimeRpcCallError +} from '../../runtime/runtime-rpc-client' import { getSettingsForRepoRuntimeOwner } from '@/lib/repo-runtime-owner' import { settingsForProjectRowOwner } from './github-project-row-owner' import { rightSidebarShowsPullRequestData } from '@/lib/right-sidebar-visibility' @@ -418,6 +422,17 @@ function countGitHubWorkItemsForRepo( }) } +function isGitHubUnavailableWorkItemsError(error: unknown): boolean { + // Why: remote-runtime transport failures can also say "timed out" or + // "unavailable". Only runtime_error came from the GitHub method itself; + // other RPC codes must not be attributed to GitHub. + if (error instanceof RuntimeRpcCallError && error.code !== 'runtime_error') { + return false + } + const message = error instanceof Error ? error.message : String(error) + return classifyGitHubUnavailable(message) !== null +} + export function projectViewCacheKey( ownerType: GetProjectViewTableArgs['ownerType'], owner: string, @@ -2887,7 +2902,7 @@ export const createGitHubSlice: StateCreator = (s return [] as GitHubWorkItem[] } requestFailureCount += 1 - if (classifyGitHubUnavailable(err instanceof Error ? err.message : String(err))) { + if (isGitHubUnavailableWorkItemsError(err)) { unavailableFailureCount += 1 } const key =