Use worktree path to resolve HEAD OID for merged PR lookups (#8349)

- Pass the active `worktreePath` through IPC, RPC, and the GitHub client
  to ensure we fetch the correct HEAD OID when resolving merged PRs.
- Validate incoming worktree paths against known repository worktrees in
  the main process to prevent forged path usage.
- Escalate Checks Panel PR refresh requests from 'swr' to 'active' when
  a cached "no PR" miss predates when the panel became visible.
This commit is contained in:
Jinjing 2026-07-11 20:45:31 -07:00 committed by GitHub
parent 4f9fbecb9e
commit ff98936c4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 1 deletions

View File

@ -124,6 +124,7 @@ import {
shouldPollChecksPanelRuntimeSshStatus,
type ChecksPanelGitStatusSnapshot
} from './checks-panel-git-status-snapshot'
import { resolveChecksPanelPRRefreshRequest } from './checks-panel-pr-refresh-request'
import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval'
import { useMountedRef } from '@/hooks/useMountedRef'
import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client'
@ -471,6 +472,7 @@ export default function ChecksPanel(): React.JSX.Element {
const confirm = useConfirmationDialog()
const prevChecksRef = useRef<string>('')
const conflictSummaryRefreshKeyRef = useRef<string | null>(null)
const panelVisibleSinceRef = useRef<number | null>(null)
commentsRef.current = comments
const prGenerationRecords = useAppStore((s) => s.pullRequestGenerationRecords)
const allocatePullRequestGenerationRequestId = useAppStore(
@ -652,6 +654,7 @@ export default function ChecksPanel(): React.JSX.Element {
// the entry for the active repo and branch.
const prCacheEntry = useAppStore((s) => selectReviewCacheEntry(s.prCache, prCacheKey || null))
const pr: PRInfo | null = prCacheEntry?.data ?? null
const prCachedHasPR = prCacheEntry ? prCacheEntry.data !== null : null
const hostedReview = useAppStore((s) =>
hostedReviewCacheKey ? (s.hostedReviewCache[hostedReviewCacheKey]?.data ?? null) : null
)
@ -736,6 +739,14 @@ export default function ChecksPanel(): React.JSX.Element {
repo?.id
])
useEffect(() => {
if (!isPanelVisible) {
panelVisibleSinceRef.current = null
return
}
panelVisibleSinceRef.current = Date.now()
}, [isPanelVisible, panelContextKey])
// Why: select only timestamps (not whole cache records) so the entry-refresh
// effect doesn't re-run on every cache mutation. See
// docs/refresh-on-checks-tab.md.
@ -1221,7 +1232,12 @@ export default function ChecksPanel(): React.JSX.Element {
staleWhileRevalidate: true
})
if (activeWorktreeId && !isGitLabReviewContext) {
enqueueGitHubPRRefresh(activeWorktreeId, 'swr', 30)
const refreshRequest = resolveChecksPanelPRRefreshRequest({
cachedHasPR: prCachedHasPR,
cachedFetchedAt: prFetchedAt ?? null,
panelVisibleSince: panelVisibleSinceRef.current
})
enqueueGitHubPRRefresh(activeWorktreeId, refreshRequest.reason, refreshRequest.priority)
}
}
}, [
@ -1239,6 +1255,8 @@ export default function ChecksPanel(): React.JSX.Element {
linkedGiteaPR,
linkedGitLabMR,
linkedPR,
prCachedHasPR,
prFetchedAt,
repo
])

View File

@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest'
import { resolveChecksPanelPRRefreshRequest } from './checks-panel-pr-refresh-request'
describe('resolveChecksPanelPRRefreshRequest', () => {
it('uses an active refresh for a cached miss from before the checks panel became visible', () => {
expect(
resolveChecksPanelPRRefreshRequest({
cachedHasPR: false,
cachedFetchedAt: 100,
panelVisibleSince: 200
})
).toEqual({ reason: 'active', priority: 80 })
})
it('keeps fresh empty lookups on the background path', () => {
expect(
resolveChecksPanelPRRefreshRequest({
cachedHasPR: false,
cachedFetchedAt: 200,
panelVisibleSince: 100
})
).toEqual({ reason: 'swr', priority: 30 })
})
it('keeps populated or unknown cache entries on the background path', () => {
expect(
resolveChecksPanelPRRefreshRequest({
cachedHasPR: true,
cachedFetchedAt: 100,
panelVisibleSince: 200
})
).toEqual({ reason: 'swr', priority: 30 })
expect(
resolveChecksPanelPRRefreshRequest({
cachedHasPR: null,
cachedFetchedAt: null,
panelVisibleSince: 200
})
).toEqual({ reason: 'swr', priority: 30 })
})
})

View File

@ -0,0 +1,30 @@
import type { GitHubPRRefreshReason } from '../../../../shared/types'
type ChecksPanelPRRefreshRequestInput = {
cachedHasPR: boolean | null
cachedFetchedAt: number | null
panelVisibleSince: number | null
}
type ChecksPanelPRRefreshRequest = {
reason: GitHubPRRefreshReason
priority: number
}
export function resolveChecksPanelPRRefreshRequest(
input: ChecksPanelPRRefreshRequestInput
): ChecksPanelPRRefreshRequest {
const cachedMissPredatesVisiblePanel =
input.cachedHasPR === false &&
input.cachedFetchedAt !== null &&
input.panelVisibleSince !== null &&
input.cachedFetchedAt < input.panelVisibleSince
if (cachedMissPredatesVisiblePanel) {
// Why: external agents can create/merge a PR after Orca cached "none";
// visible empty-state checks need one foreground lookup to recover.
return { reason: 'active', priority: 80 }
}
return { reason: 'swr', priority: 30 }
}