Don't enqueue local PR refresh for remote-host repos (fix renderer OOM crash) (#6094)
* Don't enqueue local PR refresh for remote-host repos (fix renderer OOM) The renderer enqueues GitHub PR refreshes for worktrees. Remote/SSH/runtime worktrees are meant to refresh through the runtime route (getRuntimeRepoTarget), but when that route is unavailable — host not the active environment, or disconnected — the call falls through to the local `gh:enqueuePRRefresh` IPC. The local handler only resolves repos registered in the local store, so it rejects every such call with "Access denied: unknown repository path". With a remote worktree active (e.g. a runtime "Project server" workspace), this fires on a loop (worktree activation + SWR polling), flooding the renderer with failed invokes and unhandled rejections. Observed hundreds of these per session; the renderer JS heap climbs to the V8 ceiling (~3.5GB) and V8 aborts the process (crash-reports.json: renderer crash, exitCode 5, usedHeapMB == heapLimitMB). Add isLocalHostPRRefreshCandidate and gate all four enqueue sites on it, so the local handler only ever receives local-host candidates. Remote candidates with no available runtime route are skipped (their PR status refreshes once the host is active/reconnected) instead of spamming a handler that can't serve them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: cover local executionHostId in PR-refresh host guard Address CodeRabbit nitpick on #6094: add a positive case where a local repo carries an explicit executionHostId === LOCAL_EXECUTION_HOST_ID, completing predicate coverage for isLocalHostPRRefreshCandidate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: route PR refreshes by repo host Co-authored-by: Orca <help@stably.ai> * fix: skip disconnected SSH PR refreshes Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
f5b50b6091
commit
bf2a6c1040
|
|
@ -0,0 +1,123 @@
|
|||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { create } from 'zustand'
|
||||
import { createGitHubSlice } from './github'
|
||||
import { createHostedReviewSlice } from './hosted-review'
|
||||
import type { AppState } from '../types'
|
||||
import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
|
||||
|
||||
// Why: regression guard for the renderer OOM crash. Enqueuing the local
|
||||
// `gh:enqueuePRRefresh` for a repo owned by a remote/SSH/runtime host rejects
|
||||
// with "Access denied: unknown repository path"; a flood of those failures grew
|
||||
// the renderer heap to the V8 ceiling and crashed it. enqueueGitHubPRRefresh
|
||||
// must only hit the local handler for local-host repos.
|
||||
const enqueuePRRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
const mockApi = {
|
||||
gh: {
|
||||
prForBranch: vi.fn().mockResolvedValue(null),
|
||||
enqueuePRRefresh,
|
||||
issue: vi.fn().mockResolvedValue(null)
|
||||
},
|
||||
hostedReview: { forBranch: vi.fn().mockResolvedValue(null) },
|
||||
runtimeEnvironments: { call: vi.fn() }
|
||||
}
|
||||
|
||||
// @ts-expect-error test window mock
|
||||
globalThis.window = { api: mockApi }
|
||||
|
||||
function createTestStore() {
|
||||
return create<AppState>()(
|
||||
(...a) =>
|
||||
({
|
||||
...createGitHubSlice(...a),
|
||||
...createHostedReviewSlice(...a)
|
||||
}) as AppState
|
||||
)
|
||||
}
|
||||
|
||||
function seed(store: ReturnType<typeof createTestStore>, repo: Record<string, unknown>) {
|
||||
store.setState({
|
||||
settings: { activeRuntimeEnvironmentId: null } as never,
|
||||
repos: [repo],
|
||||
worktreesByRepo: {
|
||||
[repo.id as string]: [
|
||||
{
|
||||
id: 'wt-1',
|
||||
repoId: repo.id,
|
||||
path: `${repo.path}/wt`,
|
||||
branch: 'refs/heads/feature',
|
||||
displayName: 'feature',
|
||||
isMainWorktree: false,
|
||||
isBare: false,
|
||||
isArchived: false,
|
||||
linkedPR: null,
|
||||
linkedIssue: null
|
||||
}
|
||||
]
|
||||
},
|
||||
prCache: {},
|
||||
issueCache: {},
|
||||
hostedReviewCache: {},
|
||||
sshConnectionStates: new Map()
|
||||
} as unknown as Partial<AppState>)
|
||||
}
|
||||
|
||||
describe('enqueueGitHubPRRefresh host guard', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('enqueues the local handler for a local-host repo', () => {
|
||||
const store = createTestStore()
|
||||
seed(store, { id: 'local-1', path: '/Users/me/code/local-1', name: 'local-1', kind: 'git' })
|
||||
|
||||
store.getState().enqueueGitHubPRRefresh('wt-1', 'active', 80)
|
||||
|
||||
expect(enqueuePRRefresh).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('enqueues the local handler for a repo with an explicit local executionHostId', () => {
|
||||
const store = createTestStore()
|
||||
seed(store, {
|
||||
id: 'local-2',
|
||||
path: '/Users/me/code/local-2',
|
||||
name: 'local-2',
|
||||
kind: 'git',
|
||||
executionHostId: LOCAL_EXECUTION_HOST_ID
|
||||
})
|
||||
|
||||
store.getState().enqueueGitHubPRRefresh('wt-1', 'active', 80)
|
||||
|
||||
expect(enqueuePRRefresh).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does NOT enqueue the local handler for a runtime-host repo (the OOM loop)', () => {
|
||||
const store = createTestStore()
|
||||
seed(store, {
|
||||
id: 'rt-1',
|
||||
path: '/Users/lobster/orca/workspaces/openclaw/imessage-performance',
|
||||
name: 'imessage-performance',
|
||||
kind: 'git',
|
||||
executionHostId: 'runtime:env-1'
|
||||
})
|
||||
|
||||
store.getState().enqueueGitHubPRRefresh('wt-1', 'active', 80)
|
||||
|
||||
expect(enqueuePRRefresh).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does NOT enqueue the local handler for an SSH repo', () => {
|
||||
const store = createTestStore()
|
||||
seed(store, {
|
||||
id: 'ssh-1',
|
||||
path: '/home/me/code/ssh-1',
|
||||
name: 'ssh-1',
|
||||
kind: 'git',
|
||||
connectionId: 'conn-1'
|
||||
})
|
||||
|
||||
store.getState().enqueueGitHubPRRefresh('wt-1', 'active', 80)
|
||||
|
||||
expect(enqueuePRRefresh).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { create } from 'zustand'
|
||||
import { createGitHubSlice } from './github'
|
||||
import { createHostedReviewSlice } from './hosted-review'
|
||||
import type { AppState } from '../types'
|
||||
import type { PRInfo, Repo, Worktree } from '../../../../shared/types'
|
||||
import {
|
||||
createCompatibleRuntimeStatusResponseIfNeeded,
|
||||
type RuntimeEnvironmentCallRequest
|
||||
} from '../../runtime/runtime-compatibility-test-fixture'
|
||||
import { clearRuntimeCompatibilityCacheForTests } from '../../runtime/runtime-rpc-client'
|
||||
|
||||
const runtimeEnvironmentCall = vi.fn()
|
||||
const runtimeEnvironmentTransportCall = vi.fn()
|
||||
const enqueuePRRefresh = vi.fn().mockResolvedValue(undefined)
|
||||
const reportVisiblePRRefreshCandidates = vi.fn().mockResolvedValue(true)
|
||||
|
||||
const mockApi = {
|
||||
gh: {
|
||||
prForBranch: vi.fn().mockResolvedValue(null),
|
||||
refreshPRNow: vi.fn().mockResolvedValue({ kind: 'no-pr', fetchedAt: 1 }),
|
||||
enqueuePRRefresh,
|
||||
reportVisiblePRRefreshCandidates,
|
||||
issue: vi.fn().mockResolvedValue(null)
|
||||
},
|
||||
hostedReview: { forBranch: vi.fn().mockResolvedValue(null) },
|
||||
runtimeEnvironments: { call: runtimeEnvironmentTransportCall },
|
||||
cache: {
|
||||
getGitHub: vi.fn().mockResolvedValue(null),
|
||||
setGitHub: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error test window mock
|
||||
globalThis.window = { api: mockApi }
|
||||
|
||||
function resetRuntimeMocks(): void {
|
||||
clearRuntimeCompatibilityCacheForTests()
|
||||
runtimeEnvironmentCall.mockReset()
|
||||
runtimeEnvironmentTransportCall.mockReset()
|
||||
runtimeEnvironmentTransportCall.mockImplementation((args: RuntimeEnvironmentCallRequest) => {
|
||||
return createCompatibleRuntimeStatusResponseIfNeeded(args) ?? runtimeEnvironmentCall(args)
|
||||
})
|
||||
}
|
||||
|
||||
function createTestStore() {
|
||||
return create<AppState>()(
|
||||
(...a) =>
|
||||
({
|
||||
...createGitHubSlice(...a),
|
||||
...createHostedReviewSlice(...a)
|
||||
}) as AppState
|
||||
)
|
||||
}
|
||||
|
||||
function makePR(overrides: Partial<PRInfo> = {}): PRInfo {
|
||||
return {
|
||||
number: 12,
|
||||
title: 'Test PR',
|
||||
state: 'open',
|
||||
url: 'https://example.com/pr/12',
|
||||
checksStatus: 'pending',
|
||||
updatedAt: '2026-03-28T00:00:00Z',
|
||||
mergeable: 'UNKNOWN',
|
||||
headSha: 'head-oid',
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
function makeRepo(overrides: Partial<Repo> & Pick<Repo, 'id' | 'path'>): Repo {
|
||||
return {
|
||||
displayName: overrides.id,
|
||||
badgeColor: 'blue',
|
||||
addedAt: 1,
|
||||
kind: 'git',
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
function makeWorktree(repoId: string, branch: string, id = `${repoId}-wt`): Worktree {
|
||||
return {
|
||||
id,
|
||||
repoId,
|
||||
path: `/worktrees/${id}`,
|
||||
head: 'head-oid',
|
||||
branch,
|
||||
displayName: branch,
|
||||
comment: '',
|
||||
linkedIssue: null,
|
||||
linkedPR: null,
|
||||
linkedLinearIssue: null,
|
||||
linkedLinearIssueWorkspaceId: null,
|
||||
linkedLinearIssueOrganizationUrlKey: null,
|
||||
isMainWorktree: false,
|
||||
isBare: false,
|
||||
isArchived: false,
|
||||
isUnread: false,
|
||||
isPinned: false,
|
||||
sortOrder: 1,
|
||||
lastActivityAt: 1
|
||||
}
|
||||
}
|
||||
|
||||
function seed(
|
||||
store: ReturnType<typeof createTestStore>,
|
||||
state: Pick<AppState, 'repos' | 'worktreesByRepo'> & Partial<AppState>
|
||||
): void {
|
||||
store.setState({
|
||||
settings: { activeRuntimeEnvironmentId: null } as AppState['settings'],
|
||||
groupBy: 'pr-status',
|
||||
worktreeCardProperties: ['status'],
|
||||
prCache: {},
|
||||
issueCache: {},
|
||||
hostedReviewCache: {},
|
||||
commentsCache: {},
|
||||
sshConnectionStates: new Map(),
|
||||
...state
|
||||
} as unknown as Partial<AppState>)
|
||||
}
|
||||
|
||||
describe('GitHub PR refresh owner-host routing', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
resetRuntimeMocks()
|
||||
})
|
||||
|
||||
it('routes explicit PR refresh for a runtime-owned repo to its owner while Local desktop is active', async () => {
|
||||
runtimeEnvironmentCall.mockResolvedValueOnce({
|
||||
id: 'rpc-1',
|
||||
ok: true,
|
||||
result: makePR({ number: 23 }),
|
||||
_meta: { runtimeId: 'remote-runtime' }
|
||||
})
|
||||
const store = createTestStore()
|
||||
const repoPath = '/runtime/repo'
|
||||
const branch = 'feature/runtime-owner'
|
||||
seed(store, {
|
||||
settings: { activeRuntimeEnvironmentId: null } as AppState['settings'],
|
||||
repos: [
|
||||
makeRepo({
|
||||
id: 'repo-runtime',
|
||||
path: repoPath,
|
||||
executionHostId: 'runtime:env-1'
|
||||
})
|
||||
],
|
||||
worktreesByRepo: {
|
||||
'repo-runtime': [makeWorktree('repo-runtime', branch, 'wt-runtime')]
|
||||
}
|
||||
})
|
||||
|
||||
store.getState().enqueueGitHubPRRefresh('wt-runtime', 'active', 80)
|
||||
|
||||
await vi.waitFor(() => expect(runtimeEnvironmentCall).toHaveBeenCalledTimes(1))
|
||||
expect(enqueuePRRefresh).not.toHaveBeenCalled()
|
||||
expect(runtimeEnvironmentCall).toHaveBeenCalledWith({
|
||||
selector: 'env-1',
|
||||
method: 'github.prForBranch',
|
||||
params: { repo: 'repo-runtime', branch, linkedPRNumber: null },
|
||||
timeoutMs: 30_000
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps connected SSH PR refresh on the local coordinator even when a runtime is focused', () => {
|
||||
const store = createTestStore()
|
||||
const repoPath = '/ssh/repo'
|
||||
const branch = 'feature/ssh'
|
||||
seed(store, {
|
||||
settings: { activeRuntimeEnvironmentId: 'env-focused' } as AppState['settings'],
|
||||
repos: [
|
||||
makeRepo({
|
||||
id: 'repo-ssh',
|
||||
path: repoPath,
|
||||
connectionId: 'ssh-1',
|
||||
executionHostId: 'ssh:ssh-1'
|
||||
})
|
||||
],
|
||||
sshConnectionStates: new Map([
|
||||
['ssh-1', { targetId: 'ssh-1', status: 'connected', error: null, reconnectAttempt: 0 }]
|
||||
]),
|
||||
worktreesByRepo: {
|
||||
'repo-ssh': [makeWorktree('repo-ssh', branch, 'wt-ssh')]
|
||||
}
|
||||
})
|
||||
|
||||
store.getState().refreshGitHubForWorktreeIfStale('wt-ssh')
|
||||
|
||||
expect(runtimeEnvironmentCall).not.toHaveBeenCalled()
|
||||
expect(enqueuePRRefresh).toHaveBeenCalledWith({
|
||||
candidate: expect.objectContaining({
|
||||
repoId: 'repo-ssh',
|
||||
repoPath,
|
||||
branch,
|
||||
connectionId: 'ssh-1',
|
||||
connectionState: 'connected'
|
||||
}),
|
||||
reason: 'active',
|
||||
priority: 80
|
||||
})
|
||||
})
|
||||
|
||||
it('routes post-push refresh for a runtime-owned repo to its owner while Local desktop is active', async () => {
|
||||
runtimeEnvironmentCall.mockResolvedValueOnce({
|
||||
id: 'rpc-1',
|
||||
ok: true,
|
||||
result: makePR({ number: 24 }),
|
||||
_meta: { runtimeId: 'remote-runtime' }
|
||||
})
|
||||
const store = createTestStore()
|
||||
const repoPath = '/runtime/repo'
|
||||
const branch = 'feature/post-push'
|
||||
seed(store, {
|
||||
repos: [
|
||||
makeRepo({
|
||||
id: 'repo-runtime',
|
||||
path: repoPath,
|
||||
executionHostId: 'runtime:env-1'
|
||||
})
|
||||
],
|
||||
worktreesByRepo: {
|
||||
'repo-runtime': [makeWorktree('repo-runtime', branch, 'wt-runtime')]
|
||||
}
|
||||
})
|
||||
|
||||
store.getState().refreshGitHubForWorktree('wt-runtime')
|
||||
|
||||
await vi.waitFor(() => expect(runtimeEnvironmentCall).toHaveBeenCalledTimes(1))
|
||||
expect(enqueuePRRefresh).not.toHaveBeenCalled()
|
||||
expect(runtimeEnvironmentCall).toHaveBeenCalledWith({
|
||||
selector: 'env-1',
|
||||
method: 'github.prForBranch',
|
||||
params: { repo: 'repo-runtime', branch, linkedPRNumber: null },
|
||||
timeoutMs: 30_000
|
||||
})
|
||||
})
|
||||
|
||||
it('splits visible candidates between local coordinator and runtime owner', async () => {
|
||||
runtimeEnvironmentCall.mockResolvedValueOnce({
|
||||
id: 'rpc-1',
|
||||
ok: true,
|
||||
result: makePR({ number: 25 }),
|
||||
_meta: { runtimeId: 'remote-runtime' }
|
||||
})
|
||||
const store = createTestStore()
|
||||
seed(store, {
|
||||
repos: [
|
||||
makeRepo({ id: 'repo-local', path: '/local/repo' }),
|
||||
makeRepo({
|
||||
id: 'repo-runtime',
|
||||
path: '/runtime/repo',
|
||||
executionHostId: 'runtime:env-1'
|
||||
})
|
||||
],
|
||||
worktreesByRepo: {
|
||||
'repo-local': [makeWorktree('repo-local', 'feature/local', 'wt-local')],
|
||||
'repo-runtime': [makeWorktree('repo-runtime', 'feature/runtime', 'wt-runtime')]
|
||||
}
|
||||
})
|
||||
|
||||
store.getState().reportVisibleGitHubPRRefreshCandidates(['wt-local', 'wt-runtime'], 123)
|
||||
|
||||
await vi.waitFor(() => expect(runtimeEnvironmentCall).toHaveBeenCalledTimes(1))
|
||||
expect(reportVisiblePRRefreshCandidates).toHaveBeenCalledWith({
|
||||
candidates: [
|
||||
expect.objectContaining({
|
||||
repoId: 'repo-local',
|
||||
repoPath: '/local/repo',
|
||||
branch: 'feature/local'
|
||||
})
|
||||
],
|
||||
generation: 123
|
||||
})
|
||||
expect(runtimeEnvironmentCall).toHaveBeenCalledWith({
|
||||
selector: 'env-1',
|
||||
method: 'github.prForBranch',
|
||||
params: { repo: 'repo-runtime', branch: 'feature/runtime', linkedPRNumber: null },
|
||||
timeoutMs: 30_000
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -130,6 +130,46 @@ function getRuntimeRepoTarget(
|
|||
return repo ? { target, repo } : null
|
||||
}
|
||||
|
||||
function getPRRefreshOwnerRuntimeEnvironmentId(
|
||||
candidate: Pick<GitHubPRRefreshCandidate, 'cacheKey' | 'executionHostId'>
|
||||
): string | null {
|
||||
const parsed = parseExecutionHostId(candidate.executionHostId)
|
||||
if (parsed?.kind === 'runtime') {
|
||||
return parsed.environmentId
|
||||
}
|
||||
const cacheScope = candidate.cacheKey.split('::', 1)[0]
|
||||
const cacheScopeHost = parseExecutionHostId(cacheScope)
|
||||
return cacheScopeHost?.kind === 'runtime' ? cacheScopeHost.environmentId : null
|
||||
}
|
||||
|
||||
function getPRRefreshRuntimeRepoTarget(
|
||||
state: AppState,
|
||||
candidate: GitHubPRRefreshCandidate
|
||||
): { target: { kind: 'environment'; environmentId: string }; repo: Repo } | null {
|
||||
const ownerRuntimeEnvironmentId = getPRRefreshOwnerRuntimeEnvironmentId(candidate)
|
||||
if (!ownerRuntimeEnvironmentId) {
|
||||
return null
|
||||
}
|
||||
// Why: PR refreshes must follow the repo owner host, not the Active Server
|
||||
// dropdown. A runtime-owned worktree can be visible while Local desktop is focused.
|
||||
return getRuntimeRepoTarget(
|
||||
state,
|
||||
candidate.repoPath,
|
||||
state.settings
|
||||
? { ...state.settings, activeRuntimeEnvironmentId: ownerRuntimeEnvironmentId }
|
||||
: ({ activeRuntimeEnvironmentId: ownerRuntimeEnvironmentId } as AppState['settings'])
|
||||
)
|
||||
}
|
||||
|
||||
function shouldEnqueueLocalPRRefresh(candidate: GitHubPRRefreshCandidate): boolean {
|
||||
// Why: the local PR coordinator owns local git and SSH bridge refreshes, but
|
||||
// runtime-owned repos and disconnected SSH repos must not hit the IPC crash path.
|
||||
if (getPRRefreshOwnerRuntimeEnvironmentId(candidate) !== null) {
|
||||
return false
|
||||
}
|
||||
return !candidate.connectionId || candidate.connectionState === 'connected'
|
||||
}
|
||||
|
||||
type GitHubWorkItemRequestContext = {
|
||||
repoId: string
|
||||
repoPath: string
|
||||
|
|
@ -3383,7 +3423,7 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (s
|
|||
if (!candidate) {
|
||||
return
|
||||
}
|
||||
if (getRuntimeRepoTarget(state, candidate.repoPath)) {
|
||||
if (getPRRefreshRuntimeRepoTarget(state, candidate)) {
|
||||
void get().fetchPRForBranch(candidate.repoPath, candidate.branch, {
|
||||
force: bypassesGitHubPRRefreshFreshness(reason),
|
||||
repoId: candidate.repoId,
|
||||
|
|
@ -3394,6 +3434,9 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (s
|
|||
})
|
||||
return
|
||||
}
|
||||
if (!shouldEnqueueLocalPRRefresh(candidate)) {
|
||||
return
|
||||
}
|
||||
const enqueue = window.api.gh.enqueuePRRefresh
|
||||
if (enqueue) {
|
||||
void enqueue({ candidate, reason, priority })
|
||||
|
|
@ -3424,8 +3467,9 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (s
|
|||
return worktree ? buildPRRefreshCandidate(state, worktree) : null
|
||||
})
|
||||
.filter((candidate): candidate is GitHubPRRefreshCandidate => candidate !== null)
|
||||
if (getActiveRuntimeTarget(state.settings).kind === 'environment') {
|
||||
for (const candidate of candidates) {
|
||||
const localCandidates: GitHubPRRefreshCandidate[] = []
|
||||
for (const candidate of candidates) {
|
||||
if (getPRRefreshRuntimeRepoTarget(state, candidate)) {
|
||||
void get().fetchPRForBranch(candidate.repoPath, candidate.branch, {
|
||||
repoId: candidate.repoId,
|
||||
worktreeId: candidate.worktreeId,
|
||||
|
|
@ -3433,12 +3477,15 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (s
|
|||
fallbackPRNumber: candidate.fallbackPRNumber ?? null,
|
||||
fallbackPRSource: candidate.fallbackPRSource ?? null
|
||||
})
|
||||
continue
|
||||
}
|
||||
if (shouldEnqueueLocalPRRefresh(candidate)) {
|
||||
localCandidates.push(candidate)
|
||||
}
|
||||
return
|
||||
}
|
||||
const reportVisible = window.api.gh.reportVisiblePRRefreshCandidates
|
||||
if (reportVisible) {
|
||||
void reportVisible({ candidates, generation }).catch((err) => {
|
||||
void reportVisible({ candidates: localCandidates, generation }).catch((err) => {
|
||||
console.warn('Failed to report visible PR refresh candidates:', err)
|
||||
})
|
||||
}
|
||||
|
|
@ -3733,7 +3780,7 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (s
|
|||
fallbackPRNumber: candidate.fallbackPRNumber ?? null,
|
||||
fallbackPRSource: candidate.fallbackPRSource ?? null
|
||||
})
|
||||
} else {
|
||||
} else if (shouldEnqueueLocalPRRefresh(candidate)) {
|
||||
void window.api.gh.enqueuePRRefresh?.({ candidate, reason: 'swr', priority: 10 })
|
||||
}
|
||||
}
|
||||
|
|
@ -3797,7 +3844,7 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (s
|
|||
if (!worktree.isBare && branch) {
|
||||
const candidate = buildPRRefreshCandidate(get(), worktree)
|
||||
if (candidate) {
|
||||
if (getRuntimeRepoTarget(get(), candidate.repoPath)) {
|
||||
if (getPRRefreshRuntimeRepoTarget(get(), candidate)) {
|
||||
void get().fetchPRForBranch(candidate.repoPath, candidate.branch, {
|
||||
force: true,
|
||||
repoId: candidate.repoId,
|
||||
|
|
@ -3806,7 +3853,7 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (s
|
|||
fallbackPRNumber: candidate.fallbackPRNumber ?? null,
|
||||
fallbackPRSource: candidate.fallbackPRSource ?? null
|
||||
})
|
||||
} else {
|
||||
} else if (shouldEnqueueLocalPRRefresh(candidate)) {
|
||||
void window.api.gh.enqueuePRRefresh?.({ candidate, reason: 'post-push', priority: 100 })
|
||||
}
|
||||
}
|
||||
|
|
@ -3995,7 +4042,7 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (s
|
|||
if (shouldRefreshPR && !worktree.isBare && branch) {
|
||||
const candidate = buildPRRefreshCandidate(state, worktree)
|
||||
if (candidate) {
|
||||
if (getRuntimeRepoTarget(state, candidate.repoPath)) {
|
||||
if (getPRRefreshRuntimeRepoTarget(state, candidate)) {
|
||||
void get().fetchPRForBranch(candidate.repoPath, candidate.branch, {
|
||||
force: true,
|
||||
repoId: candidate.repoId,
|
||||
|
|
@ -4004,7 +4051,7 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (s
|
|||
fallbackPRNumber: candidate.fallbackPRNumber ?? null,
|
||||
fallbackPRSource: candidate.fallbackPRSource ?? null
|
||||
})
|
||||
} else {
|
||||
} else if (shouldEnqueueLocalPRRefresh(candidate)) {
|
||||
void window.api.gh.enqueuePRRefresh?.({ candidate, reason: 'active', priority: 80 })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue