authorgravatar for saiansh2525@gmail.comYash Singh <saiansh2525@gmail.com> 2026-09-06 20:08:43-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-06 20:08:43-05:00
log1e740e48a5f91b3edd35c5446d33b0d36fb5a6ac
treeae4bf79ad0449afbf337604937758b6a2a577c8b
parentefeac144289d0f64bcfe3b9274c616499893be91
signature Signed by PGP key B5690EEEBB952194

fix(server): follow placeholder branches after checkout updates (#10441)


2 files changed, 40 insertions(+), 10 deletions(-)

apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+39-8
...@@ -1002,23 +1002,22 @@ describe("CheckpointReactor", () => {...@@ -1002,23 +1002,22 @@ describe("CheckpointReactor", () => {
1002 expect(thread?.branch).toBe("t3code/renamed-by-agent");1002 expect(thread?.branch).toBe("t3code/renamed-by-agent");
1003 });1003 });
10041004
1005 it("does not adopt a drifted checkout when the worktree is shared by another thread", async () => {1005 it("follows a checkout from a saved placeholder branch and refreshes its pull request", async () => {
1006 const pullRequestRefreshCalls: string[] = [];1006 const pullRequestRefreshCalls: string[] = [];
1007 const harness = await createHarness({1007 const harness = await createHarness({
1008 seedFilesystemCheckpoints: false,1008 seedFilesystemCheckpoints: false,
1009 threadBranch: "t3code/original-branch",1009 threadBranch: "t3code/fd9cbe0e",
1010 localStatusRefName: "t3code/renamed-by-agent",1010 localStatusRefName: "fix/mobile-tool-detail-expansion",
1011 secondThreadSharingWorktree: true,
1012 pullRequestRefreshCalls,1011 pullRequestRefreshCalls,
1013 });1012 });
10141013
1015 harness.provider.emit({1014 harness.provider.emit({
1016 type: "turn.completed",1015 type: "turn.completed",
1017 eventId: EventId.make("evt-turn-completed-branch-drift-shared"),1016 eventId: EventId.make("evt-turn-completed-placeholder-drift"),
1018 provider: ProviderDriverKind.make("codex"),1017 provider: ProviderDriverKind.make("codex"),
1019 createdAt: "2026-01-01T00:00:00.000Z",1018 createdAt: "2026-01-01T00:00:00.000Z",
1020 threadId: ThreadId.make("thread-1"),1019 threadId: ThreadId.make("thread-1"),
1021 turnId: asTurnId("turn-branch-drift-shared"),1020 turnId: asTurnId("turn-placeholder-drift"),
1022 payload: { state: "completed" },1021 payload: { state: "completed" },
1023 });1022 });
10241023
...@@ -1026,10 +1025,42 @@ describe("CheckpointReactor", () => {...@@ -1026,10 +1025,42 @@ describe("CheckpointReactor", () => {
10261025
1027 const snapshot = await harness.readModel();1026 const snapshot = await harness.readModel();
1028 const thread = snapshot.threads.find((entry) => entry.id === ThreadId.make("thread-1"));1027 const thread = snapshot.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
1029 expect(thread?.branch).toBe("t3code/original-branch");1028 expect(thread?.branch).toBe("fix/mobile-tool-detail-expansion");
1030 expect(pullRequestRefreshCalls).toEqual([]);1029 expect(thread?.worktreePath).toBe(harness.cwd);
1030 expect(pullRequestRefreshCalls).toEqual([harness.cwd]);
1031 });1031 });
10321032
1033 it.each(["t3code/original-branch", "t3code/fd9cbe0e"])(
1034 "does not adopt a drifted checkout from %s when the worktree is shared by another thread",
1035 async (threadBranch) => {
1036 const pullRequestRefreshCalls: string[] = [];
1037 const harness = await createHarness({
1038 seedFilesystemCheckpoints: false,
1039 threadBranch,
1040 localStatusRefName: "t3code/renamed-by-agent",
1041 secondThreadSharingWorktree: true,
1042 pullRequestRefreshCalls,
1043 });
1044
1045 harness.provider.emit({
1046 type: "turn.completed",
1047 eventId: EventId.make("evt-turn-completed-branch-drift-shared"),
1048 provider: ProviderDriverKind.make("codex"),
1049 createdAt: "2026-01-01T00:00:00.000Z",
1050 threadId: ThreadId.make("thread-1"),
1051 turnId: asTurnId("turn-branch-drift-shared"),
1052 payload: { state: "completed" },
1053 });
1054
1055 await harness.drain();
1056
1057 const snapshot = await harness.readModel();
1058 const thread = snapshot.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
1059 expect(thread?.branch).toBe(threadBranch);
1060 expect(pullRequestRefreshCalls).toEqual([]);
1061 },
1062 );
1063
1033 it("does not adopt a temporary placeholder checkout as the thread branch", async () => {1064 it("does not adopt a temporary placeholder checkout as the thread branch", async () => {
1034 const harness = await createHarness({1065 const harness = await createHarness({
1035 seedFilesystemCheckpoints: false,1066 seedFilesystemCheckpoints: false,
apps/server/src/orchestration/Layers/CheckpointReactor.ts+1-2
...@@ -563,8 +563,7 @@ const make = Effect.gen(function* () {...@@ -563,8 +563,7 @@ const make = Effect.gen(function* () {
563 thread.branch === null ||563 thread.branch === null ||
564 thread.branch === checkedOutBranch ||564 thread.branch === checkedOutBranch ||
565 thread.worktreePath === null ||565 thread.worktreePath === null ||
566 thread.worktreePath !== input.cwd ||566 thread.worktreePath !== input.cwd
567 isTemporaryWorktreeBranch(thread.branch)
568 ) {567 ) {
569 return;568 return;
570 }569 }