diff --git a/packages/host-service/src/runtime/pull-requests/pull-requests.test.ts b/packages/host-service/src/runtime/pull-requests/pull-requests.test.ts index 43425970e36..68a73096f66 100644 --- a/packages/host-service/src/runtime/pull-requests/pull-requests.test.ts +++ b/packages/host-service/src/runtime/pull-requests/pull-requests.test.ts @@ -350,4 +350,34 @@ describe("PullRequestRuntimeManager direct checkout PR linking", () => { }, ]); }); + + test("preserves existing pullRequestId when head lookup fails", async () => { + const state = makeState("fix/sidebar"); + state.workspace = { + ...state.workspace, + headSha: "abc123", + upstreamOwner: "fork-owner", + upstreamRepo: "fork-repo", + upstreamBranch: "fix/sidebar", + pullRequestId: "pr-existing", + }; + const manager = createManager(state, { + execGh: async () => { + throw new Error("gh unavailable"); + }, + github: async () => { + throw new Error("octokit unavailable"); + }, + }); + + const originalWarn = console.warn; + console.warn = () => {}; + try { + await manager.refreshPullRequestsByWorkspaces([WORKSPACE_ID]); + } finally { + console.warn = originalWarn; + } + + expect(state.workspace.pullRequestId).toBe("pr-existing"); + }); }); diff --git a/packages/host-service/src/runtime/pull-requests/pull-requests.ts b/packages/host-service/src/runtime/pull-requests/pull-requests.ts index f400563ddb3..37ee51783ee 100644 --- a/packages/host-service/src/runtime/pull-requests/pull-requests.ts +++ b/packages/host-service/src/runtime/pull-requests/pull-requests.ts @@ -638,12 +638,8 @@ export class PullRequestRuntimeManager { } } - const keyToPullRequest = await this.fetchRepoPullRequests( - projectId, - repo, - wantedRefs, - options, - ); + const { failedKeys, matched: keyToPullRequest } = + await this.fetchRepoPullRequests(projectId, repo, wantedRefs, options); for (const workspace of projectWorkspaces) { const key = upstreamKey( @@ -673,9 +669,20 @@ export class PullRequestRuntimeManager { continue; } const match = keyToPullRequest.get(key); + if (match) { + this.db + .update(workspaces) + .set({ pullRequestId: match.id }) + .where(eq(workspaces.id, workspace.id)) + .run(); + continue; + } + + if (failedKeys.has(key)) continue; + this.db .update(workspaces) - .set({ pullRequestId: match?.id ?? null }) + .set({ pullRequestId: null }) .where(eq(workspaces.id, workspace.id)) .run(); } @@ -918,8 +925,13 @@ export class PullRequestRuntimeManager { repo: NormalizedRepoIdentity, wantedRefs: Map, options: { bypassCache?: boolean } = {}, - ): Promise> { - if (wantedRefs.size === 0) return new Map(); + ): Promise<{ + matched: Map; + failedKeys: Set; + }> { + const matched = new Map(); + const failedKeys = new Set(); + if (wantedRefs.size === 0) return { matched, failedKeys }; const latestByKey = new Map(); await Promise.all( @@ -939,6 +951,7 @@ export class PullRequestRuntimeManager { ); if (nodeKey === key) latestByKey.set(key, node); } catch (error) { + failedKeys.add(key); console.warn( "[host-service:pull-request-runtime] Failed to fetch PR by head", { @@ -953,7 +966,6 @@ export class PullRequestRuntimeManager { }), ); - const keyToRow = new Map(); const now = Date.now(); const checksByNumber = new Map< @@ -1038,9 +1050,9 @@ export class PullRequestRuntimeManager { now, }); - keyToRow.set(key, { id: rowId }); + matched.set(key, { id: rowId }); } - return keyToRow; + return { matched, failedKeys }; } }