authorgravatar for lucenz@proton.meSimone <lucenz@proton.me> 2026-10-05 12:14:33+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-05 03:14:33-07:00
log7812230572f2d7e042d8d14ada2381fc71377560
tree88a262487469fa2237903cc687e6e983ae2e39df
parentcf3e714b0f58e29c8fa8660db50d2187e3263b65
signature Signed by PGP key B5690EEEBB952194

fix(server): subagents no longer inherit parent pull-request links (#14918)

Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com>

2 files changed, 92 insertions(+), 0 deletions(-)

apps/server/src/orchestration-v2/Orchestrator.control-reads.test.ts+90
...@@ -534,3 +534,93 @@ it.effect("settles only the stopped run's background work, once", () =>...@@ -534,3 +534,93 @@ it.effect("settles only the stopped run's background work, once", () =>
534 ]);534 ]);
535 }).pipe(Effect.provide(testLayer)),535 }).pipe(Effect.provide(testLayer)),
536);536);
537
538it.effect("keeps delegated child pull-request links independent of the parent", () =>
539 Effect.gen(function* () {
540 const orchestrator = yield* Orchestrator.OrchestratorV2;
541 const projections = yield* ProjectionStore.ProjectionStoreV2;
542 const parentThreadId = ThreadId.make("thread:parent-pr");
543 const projectId = ProjectId.make("project:parent-pr");
544 const parentPullRequest = {
545 projectId,
546 repository: "pingdotgg/t3code",
547 number: 123,
548 url: "https://github.com/pingdotgg/t3code/pull/123",
549 };
550 yield* orchestrator.dispatch({
551 type: "thread.create",
552 commandId: CommandId.make("create-parent-pr"),
553 threadId: parentThreadId,
554 projectId,
555 title: "Parent with a linked PR",
556 modelSelection,
557 runtimeMode: "full-access",
558 interactionMode: "default",
559 branch: "feature/parent-pr",
560 worktreePath: "/repo-worktree",
561 createdBy: "user",
562 creationSource: "web",
563 });
564 yield* orchestrator.dispatch({
565 type: "thread.metadata.update",
566 commandId: CommandId.make("link-parent-pr"),
567 threadId: parentThreadId,
568 linkedPullRequest: parentPullRequest,
569 });
570 yield* orchestrator.dispatch({
571 type: "message.dispatch",
572 commandId: CommandId.make("start-parent-pr"),
573 threadId: parentThreadId,
574 messageId: MessageId.make("message:parent-pr"),
575 text: "Delegate a review",
576 attachments: [],
577 dispatchMode: { type: "start_immediately" },
578 createdBy: "user",
579 creationSource: "web",
580 });
581 const parent = yield* projections.getThreadProjection(parentThreadId);
582 const parentRun = parent.runs[0]!;
583 yield* orchestrator.dispatch({
584 type: "delegated_task.request",
585 commandId: CommandId.make("delegate-parent-pr"),
586 parentThreadId,
587 parentRunId: parentRun.id,
588 parentNodeId: parentRun.rootNodeId!,
589 task: "Review the changes",
590 modelSelection,
591 runtimeMode: "full-access",
592 interactionMode: "default",
593 createdBy: "agent",
594 creationSource: "mcp",
595 });
596 const updatedParent = yield* projections.getThreadProjection(parentThreadId);
597 const childThreadId = updatedParent.subagents[0]!.childThreadId!;
598 const child = yield* projections.getThreadProjection(childThreadId);
599 assert.isNull(child.thread.linkedPullRequest);
600 assert.deepEqual(child.thread.pullRequests, []);
601 assert.equal(child.thread.branch, parent.thread.branch);
602 assert.equal(child.thread.worktreePath, parent.thread.worktreePath);
603 assert.equal(child.thread.lineage.parentThreadId, parentThreadId);
604
605 const childPullRequest = {
606 ...parentPullRequest,
607 number: 456,
608 url: "https://github.com/pingdotgg/t3code/pull/456",
609 };
610 yield* orchestrator.dispatch({
611 type: "thread.metadata.update",
612 commandId: CommandId.make("link-child-pr"),
613 threadId: childThreadId,
614 linkedPullRequest: childPullRequest,
615 });
616 const linkedChild = yield* projections.getThreadProjection(childThreadId);
617 assert.deepEqual(linkedChild.thread.linkedPullRequest, childPullRequest);
618 assert.deepEqual(
619 linkedChild.thread.pullRequests?.map((link) => link.number),
620 [456],
621 );
622 const parentAfterChildLink = yield* projections.getThreadProjection(parentThreadId);
623 assert.deepEqual(parentAfterChildLink.thread.linkedPullRequest, parentPullRequest);
624 assert.deepEqual(parentAfterChildLink.thread.pullRequests, parent.thread.pullRequests);
625 }).pipe(Effect.provide(testLayer)),
626);
apps/server/src/orchestration-v2/SubagentProjection.ts+2
...@@ -57,6 +57,8 @@ export function makeSubagentChildThread(input: {...@@ -57,6 +57,8 @@ export function makeSubagentChildThread(input: {
57 creationSource: input.creationSource,57 creationSource: input.creationSource,
58 id: input.childThreadId,58 id: input.childThreadId,
59 title: input.title,59 title: input.title,
60 linkedPullRequest: null,
61 pullRequests: [],
60 historyOrigin: undefined,62 historyOrigin: undefined,
61 providerInstanceId: input.providerInstanceId,63 providerInstanceId: input.providerInstanceId,
62 modelSelection: input.modelSelection,64 modelSelection: input.modelSelection,