Skip to content

Commit 2b9af8f

Browse files
perf(server): port #13689 to V2 — post-turn PR discovery refreshes the repository identity
Main raised RepositoryIdentityResolver's positive cache TTL from 1 to 15 minutes so the per-minute sweeps stop spawning git for every project, and made PR discovery after a turn resolve with refresh: true (a turn can add the remote the pull request lives on). The TTL change and the publish-repository refresh in ws.ts merged cleanly and apply to V2. V2's ThreadPullRequestService resolves the identity itself, and did so without refresh, so after the merge it could miss a newly added remote for up to 15 minutes instead of one. Discovery requests that already carry refresh: true (run finished, checkpoint captured, unsettled) now pass it to the resolver. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 437b334 commit 2b9af8f

2 files changed

Lines changed: 34 additions & 2 deletions

File tree

‎apps/server/src/orchestration-v2/ThreadPullRequestService.test.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,31 @@ describe("ThreadPullRequestServiceV2 project guard", () => {
6868
}),
6969
);
7070

71+
it.effect("refreshes the cached repository identity only when asked", () =>
72+
Effect.gen(function* () {
73+
const project: OrchestrationProjectShell = {
74+
id: ProjectId.make("project-1"),
75+
title: "Project",
76+
workspaceRoot: "/workspace/project",
77+
defaultModelSelection: null,
78+
scripts: [],
79+
repositoryIdentity: null,
80+
createdAt: "2026-08-01T00:00:00.000Z",
81+
updatedAt: "2026-08-01T00:00:00.000Z",
82+
};
83+
const refreshes: Array<boolean | undefined> = [];
84+
const resolver = {
85+
resolve: (_root: string, options?: { readonly refresh?: boolean }) => {
86+
refreshes.push(options?.refresh);
87+
return Effect.succeed(null);
88+
},
89+
};
90+
yield* resolveProjectForPullRequestDiscovery(project, resolver);
91+
yield* resolveProjectForPullRequestDiscovery(project, resolver, { refresh: true });
92+
expect(refreshes).toEqual([false, true]);
93+
}),
94+
);
95+
7196
it("rejects a pull-request result when the project root changes before dispatch", () => {
7297
const currentProject = Option.some({
7398
workspaceRoot: "/workspace/replaced",

‎apps/server/src/orchestration-v2/ThreadPullRequestService.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,13 @@ export const resolveProjectForPullRequestDiscovery = Effect.fn(
6767
)(function* (
6868
project: OrchestrationProjectShell,
6969
repositoryIdentities: RepositoryIdentityResolver.RepositoryIdentityResolver["Service"],
70+
options?: { readonly refresh?: boolean },
7071
) {
71-
const repositoryIdentity = yield* repositoryIdentities.resolve(project.workspaceRoot);
72+
// Identities stay cached for 15 minutes. A finished turn may have added the
73+
// remote its pull request lives on, so post-turn discovery refreshes.
74+
const repositoryIdentity = yield* repositoryIdentities.resolve(project.workspaceRoot, {
75+
refresh: options?.refresh ?? false,
76+
});
7277
return {
7378
project: { ...project, repositoryIdentity },
7479
repository: sourceControlRepositorySelector(repositoryIdentity),
@@ -173,7 +178,9 @@ export const make = Effect.gen(function* () {
173178
const project = projects.get(first.projectId);
174179
if (project === undefined) return finishBackfill(group);
175180
const { project: resolvedProject, repository } =
176-
yield* resolveProjectForPullRequestDiscovery(project, repositoryIdentities);
181+
yield* resolveProjectForPullRequestDiscovery(project, repositoryIdentities, {
182+
refresh: request.refresh,
183+
});
177184
if (first.branch !== null && repository === null) return finishBackfill(group);
178185
const worktreeExists =
179186
first.worktreePath !== null && (yield* fileSystem.exists(first.worktreePath));

0 commit comments

Comments
 (0)