FN-7133: fix PR merge status repository lookup
Fix PR-mode auto-merge status checks to query GitHub with the project repository. - Resolve the current project owner/repo once from the task cwd before PR status checks. - Pass owner/repo/number to getPrMergeStatus for shared-group, task, and retry paths. - Cover repository resolution and PR status argument behavior in lifecycle tests. - Add a patch changeset for the published CLI fix. Files changed: .changeset/FN-7133-pr-merge-status-repo-args.md | 7 ++++ .../src/commands/__tests__/task-lifecycle.test.ts | 39 +++++++++++++++++++++- packages/cli/src/commands/task-lifecycle.ts | 17 +++++++--- 3 files changed, 58 insertions(+), 5 deletions(-) Fusion-Task-Id: FN-7133 Fusion-Task-Lineage: 3f9bfd65-6950-40dc-9505-53140bd6a6a1 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/FN-7133-pr-merge-status-repo-args.md
Normal file
7
.changeset/FN-7133-pr-merge-status-repo-args.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Fix PR-mode auto-merge failing with "error connecting to <branch>".
|
||||||
|
category: fix
|
||||||
|
dev: processPullRequestMergeTask now resolves owner/repo via getCurrentRepo(cwd) and passes (owner, repo, number) to getPrMergeStatus at all three call sites (shared-group, per-task, retry); the local GitHubOperations interface param names corrected from base/head to owner/repo. FN-7133.
|
||||||
@@ -36,6 +36,7 @@ vi.mock("@fusion/core", async () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
import { getCurrentRepo } from "@fusion/core";
|
||||||
import { activeSessionRegistry } from "@fusion/engine";
|
import { activeSessionRegistry } from "@fusion/engine";
|
||||||
import {
|
import {
|
||||||
cleanupMergedTaskArtifacts,
|
cleanupMergedTaskArtifacts,
|
||||||
@@ -153,6 +154,7 @@ describe("processPullRequestMergeTask", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
execMock.mockReset();
|
execMock.mockReset();
|
||||||
execFileCalls.length = 0;
|
execFileCalls.length = 0;
|
||||||
|
vi.mocked(getCurrentRepo).mockReturnValue({ owner: "owner", repo: "repo" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("pushes the per-task branch to origin before creating a new PR", async () => {
|
it("pushes the per-task branch to origin before creating a new PR", async () => {
|
||||||
@@ -421,7 +423,7 @@ describe("processPullRequestMergeTask", () => {
|
|||||||
await processPullRequestMergeTask(store as never, "/repo", task.id, github as never, () => undefined);
|
await processPullRequestMergeTask(store as never, "/repo", task.id, github as never, () => undefined);
|
||||||
|
|
||||||
expect(github.createPr).not.toHaveBeenCalled();
|
expect(github.createPr).not.toHaveBeenCalled();
|
||||||
expect(github.getPrMergeStatus).toHaveBeenCalledWith("main", "fusion/groups/p-2", 22);
|
expect(github.getPrMergeStatus).toHaveBeenCalledWith("owner", "repo", 22);
|
||||||
expect(store.updateBranchGroup).toHaveBeenCalledWith("BG-2", expect.objectContaining({
|
expect(store.updateBranchGroup).toHaveBeenCalledWith("BG-2", expect.objectContaining({
|
||||||
prNumber: 22,
|
prNumber: 22,
|
||||||
prUrl: "https://github.com/x/y/pull/22",
|
prUrl: "https://github.com/x/y/pull/22",
|
||||||
@@ -429,6 +431,38 @@ describe("processPullRequestMergeTask", () => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects before PR status checks when the project repository cannot be resolved", async () => {
|
||||||
|
vi.mocked(getCurrentRepo).mockReturnValueOnce(null);
|
||||||
|
const task: MockTask = {
|
||||||
|
id: "FN-7133",
|
||||||
|
title: "test",
|
||||||
|
description: "desc",
|
||||||
|
column: "in-review",
|
||||||
|
prInfo: {
|
||||||
|
number: 7133,
|
||||||
|
url: "https://github.com/x/y/pull/7133",
|
||||||
|
status: "open",
|
||||||
|
headBranch: "fusion/fn-7133",
|
||||||
|
baseBranch: "main",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = makeStore(task);
|
||||||
|
const github = {
|
||||||
|
findPrForBranch: vi.fn(),
|
||||||
|
createPr: vi.fn(),
|
||||||
|
getPrMergeStatus: vi.fn(),
|
||||||
|
mergePr: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
processPullRequestMergeTask(store as never, "/repo", task.id, github as never, () => undefined),
|
||||||
|
).rejects.toThrow("processPullRequestMergeTask: could not determine repository");
|
||||||
|
|
||||||
|
expect(github.getPrMergeStatus).not.toHaveBeenCalled();
|
||||||
|
expect(github.findPrForBranch).not.toHaveBeenCalled();
|
||||||
|
expect(github.createPr).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("finalizes branch group and member tasks when shared group PR is already merged", async () => {
|
it("finalizes branch group and member tasks when shared group PR is already merged", async () => {
|
||||||
const taskA: MockTask = {
|
const taskA: MockTask = {
|
||||||
id: "FN-9015",
|
id: "FN-9015",
|
||||||
@@ -594,6 +628,7 @@ describe("processPullRequestMergeTask", () => {
|
|||||||
expect(github.createPr).toHaveBeenCalledWith(expect.objectContaining({
|
expect(github.createPr).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
base: "main",
|
base: "main",
|
||||||
}));
|
}));
|
||||||
|
expect(github.getPrMergeStatus).toHaveBeenCalledWith("owner", "repo", 7);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("skips the push when an existing PR already covers the branch", async () => {
|
it("skips the push when an existing PR already covers the branch", async () => {
|
||||||
@@ -966,6 +1001,8 @@ describe("processPullRequestMergeTask", () => {
|
|||||||
expect(result).toBe("merged");
|
expect(result).toBe("merged");
|
||||||
expect(github.mergePr).toHaveBeenCalledWith({ number: 124, method: "squash" });
|
expect(github.mergePr).toHaveBeenCalledWith({ number: 124, method: "squash" });
|
||||||
expect(github.getPrMergeStatus).toHaveBeenCalledTimes(2);
|
expect(github.getPrMergeStatus).toHaveBeenCalledTimes(2);
|
||||||
|
expect(github.getPrMergeStatus).toHaveBeenNthCalledWith(1, "owner", "repo", 124);
|
||||||
|
expect(github.getPrMergeStatus).toHaveBeenNthCalledWith(2, "owner", "repo", 124);
|
||||||
expect(store.updatePrInfo).toHaveBeenLastCalledWith("FN-9104", expect.objectContaining({ status: "merged" }));
|
expect(store.updatePrInfo).toHaveBeenLastCalledWith("FN-9104", expect.objectContaining({ status: "merged" }));
|
||||||
expect(store.updateTask).toHaveBeenCalledWith("FN-9104", { status: null, mergeRetries: 0 });
|
expect(store.updateTask).toHaveBeenCalledWith("FN-9104", { status: null, mergeRetries: 0 });
|
||||||
expect(store.moveTask).toHaveBeenCalledWith("FN-9104", "done");
|
expect(store.moveTask).toHaveBeenCalledWith("FN-9104", "done");
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import type {
|
|||||||
interface GitHubOperations {
|
interface GitHubOperations {
|
||||||
findPrForBranch(params: { head: string; state?: "open" | "closed" | "all" }): Promise<PrInfo | null>;
|
findPrForBranch(params: { head: string; state?: "open" | "closed" | "all" }): Promise<PrInfo | null>;
|
||||||
createPr(params: { title: string; body: string; head: string; base?: string }): Promise<PrInfo>;
|
createPr(params: { title: string; body: string; head: string; base?: string }): Promise<PrInfo>;
|
||||||
getPrMergeStatus(base?: string, head?: string, number?: number): Promise<{
|
getPrMergeStatus(owner?: string, repo?: string, number?: number): Promise<{
|
||||||
prInfo: PrInfo;
|
prInfo: PrInfo;
|
||||||
reviewDecision: string | null;
|
reviewDecision: string | null;
|
||||||
checks: Array<{ name: string; required: boolean; state: string }>;
|
checks: Array<{ name: string; required: boolean; state: string }>;
|
||||||
@@ -667,6 +667,15 @@ export async function processPullRequestMergeTask(
|
|||||||
return "skipped";
|
return "skipped";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FNXC:PrMergeAutoMerge 2026-06-27-13:14:
|
||||||
|
* FN-7133 requires PR-mode merge status to resolve owner/repo from the project cwd because multi-project daemons cannot rely on process cwd. Never pass branch names into gh pr view --repo; getPrMergeStatus forwards its first two args as the repository slug.
|
||||||
|
*/
|
||||||
|
const prRepo = getCurrentRepo(cwd);
|
||||||
|
if (!prRepo) {
|
||||||
|
throw new Error("processPullRequestMergeTask: could not determine repository");
|
||||||
|
}
|
||||||
|
|
||||||
const branch = getTaskBranchName(task.id);
|
const branch = getTaskBranchName(task.id);
|
||||||
const settings = await store.getSettings();
|
const settings = await store.getSettings();
|
||||||
// `requirePrApproval` MOVED to workflow settings (U4): resolve the task's
|
// `requirePrApproval` MOVED to workflow settings (U4): resolve the task's
|
||||||
@@ -756,7 +765,7 @@ export async function processPullRequestMergeTask(
|
|||||||
prState: toBranchGroupPrState(groupPrInfo),
|
prState: toBranchGroupPrState(groupPrInfo),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mergeStatus = await github.getPrMergeStatus(projectDefaultBranch, branchGroup.branchName, groupPrInfo.number);
|
const mergeStatus = await github.getPrMergeStatus(prRepo.owner, prRepo.repo, groupPrInfo.number);
|
||||||
const refreshedPrInfo: PrInfo = {
|
const refreshedPrInfo: PrInfo = {
|
||||||
...groupPrInfo,
|
...groupPrInfo,
|
||||||
...mergeStatus.prInfo,
|
...mergeStatus.prInfo,
|
||||||
@@ -858,7 +867,7 @@ export async function processPullRequestMergeTask(
|
|||||||
throw new Error(`Failed to create or resolve pull request for ${task.id}`);
|
throw new Error(`Failed to create or resolve pull request for ${task.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergeStatus = await github.getPrMergeStatus(mergeTarget.branch, branch, prInfo.number);
|
const mergeStatus = await github.getPrMergeStatus(prRepo.owner, prRepo.repo, prInfo.number);
|
||||||
const refreshedPrInfo: PrInfo = {
|
const refreshedPrInfo: PrInfo = {
|
||||||
...prInfo,
|
...prInfo,
|
||||||
...mergeStatus.prInfo,
|
...mergeStatus.prInfo,
|
||||||
@@ -904,7 +913,7 @@ export async function processPullRequestMergeTask(
|
|||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
let refreshedStatus: Awaited<ReturnType<GitHubOperations["getPrMergeStatus"]>>;
|
let refreshedStatus: Awaited<ReturnType<GitHubOperations["getPrMergeStatus"]>>;
|
||||||
try {
|
try {
|
||||||
refreshedStatus = await github.getPrMergeStatus(mergeTarget.branch, branch, prInfo.number);
|
refreshedStatus = await github.getPrMergeStatus(prRepo.owner, prRepo.repo, prInfo.number);
|
||||||
} catch {
|
} catch {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user