FN-5829: add shared feature branch option for new tasks

Allow new tasks to target a shared branch-group feature branch during creation.

- Add branch-group aware branch selection handling in task workflow routes.
- Update store/types/db wiring to support and persist shared feature-branch targeting.
- Extend dashboard task creation UI/tests and route tests to cover the new option.
- Add a changeset for @runfusion/fusion documenting the new CLI/task behavior.

Files changed:
 .changeset/fn-5829-shared-feature-branch-option.md |   7 ++
 .../src/commands/__tests__/task-lifecycle.test.ts  |   2 +-
 .../core/src/__tests__/branch-group-store.test.ts  |  26 ++++++
 packages/core/src/db.ts                            |   4 +-
 packages/core/src/store.ts                         |   9 +-
 packages/core/src/types.ts                         |   2 +-
 packages/dashboard/app/components/NewTaskModal.tsx |   2 +-
 packages/dashboard/app/components/TaskForm.tsx     |   9 +-
 .../app/components/__tests__/NewTaskModal.test.tsx |  37 ++++++++
 .../src/__tests__/branch-selection.test.ts         |  12 +++
 .../dashboard/src/__tests__/mission-e2e.test.ts    |   8 +-
 .../src/__tests__/routes-planning.test.ts          |   6 +-
 .../src/__tests__/routes-tasks.test.ts             | 102 +++++++++++++++++++++
 packages/dashboard/src/routes/branch-selection.ts  |  19 +++-
 .../src/routes/register-task-workflow-routes.ts    |  23 +++--
 15 files changed, 242 insertions(+), 26 deletions(-)

Fusion-Task-Id: FN-5829

Fusion-Task-Lineage: 7d69acc8-19a3-4112-9bf2-5ac7b5e5a929
This commit is contained in:
gsxdsm
2026-06-01 03:23:10 -07:00
parent 3f75f55707
commit 9c29e2e776
15 changed files with 242 additions and 26 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": minor
---
Add a new New Task branch strategy option, **Merge into a shared feature branch** (`shared-group`).
When selected, task creation now joins an existing open branch group by shared branch name (or creates a `new-task` sourced group when missing), links `branchContext` with `assignmentMode: "shared"`, and derives a per-task working branch from the shared branch instead of running directly on the shared integration branch.

View File

@@ -37,7 +37,7 @@ interface MockTask {
baseBranch?: string;
branchContext?: {
groupId: string;
source: "planning" | "mission";
source: "planning" | "mission" | "new-task";
assignmentMode: "shared" | "per-task-derived";
inheritedBaseBranch?: string;
};

View File

@@ -67,6 +67,16 @@ describe("TaskStore branch groups", () => {
expect(second.autoMerge).toBe(true);
});
it("supports new-task branch group sources and round-trips through lookups", () => {
const group = store.ensureBranchGroupForSource("new-task", "shared/onboarding", {
branchName: "shared/onboarding",
});
expect(group.sourceType).toBe("new-task");
expect(store.getBranchGroupBySource("new-task", "shared/onboarding")?.id).toBe(group.id);
expect(store.getBranchGroup(group.id)?.sourceType).toBe("new-task");
});
it("enforces unique branchName", () => {
store.createBranchGroup({ sourceType: "mission", sourceId: "M-1", branchName: "fn/shared" });
expect(() =>
@@ -74,6 +84,22 @@ describe("TaskStore branch groups", () => {
).toThrow();
});
it("finds open branch groups by branch name and ignores closed groups", () => {
expect(store.getBranchGroupByBranchName("fn/missing")).toBeNull();
const planning = store.createBranchGroup({ sourceType: "planning", sourceId: "PS-open", branchName: "fn/open" });
expect(store.getBranchGroupByBranchName("fn/open")?.id).toBe(planning.id);
store.updateBranchGroup(planning.id, { status: "finalized" });
expect(store.getBranchGroupByBranchName("fn/open")).toBeNull();
const mission = store.createBranchGroup({ sourceType: "mission", sourceId: "M-open", branchName: "fn/mission-open" });
expect(store.getBranchGroupByBranchName("fn/mission-open")?.id).toBe(mission.id);
const newTask = store.createBranchGroup({ sourceType: "new-task", sourceId: "NT-open", branchName: "fn/new-task-open" });
expect(store.getBranchGroupByBranchName("fn/new-task-open")?.id).toBe(newTask.id);
});
it("rejects duplicate branch group primary key id", () => {
const now = Date.now();
(store as any).db

View File

@@ -787,7 +787,7 @@ CREATE TABLE IF NOT EXISTS missions (
CREATE TABLE IF NOT EXISTS branch_groups (
id TEXT PRIMARY KEY,
sourceType TEXT NOT NULL CHECK (sourceType IN ('mission','planning')),
sourceType TEXT NOT NULL CHECK (sourceType IN ('mission','planning','new-task')),
sourceId TEXT NOT NULL,
branchName TEXT NOT NULL UNIQUE,
worktreePath TEXT,
@@ -3705,7 +3705,7 @@ export class Database {
this.db.exec(`
CREATE TABLE IF NOT EXISTS branch_groups (
id TEXT PRIMARY KEY,
sourceType TEXT NOT NULL CHECK (sourceType IN ('mission','planning')),
sourceType TEXT NOT NULL CHECK (sourceType IN ('mission','planning','new-task')),
sourceId TEXT NOT NULL,
branchName TEXT NOT NULL UNIQUE,
worktreePath TEXT,

View File

@@ -191,7 +191,7 @@ function parseTaskBranchContextFromSourceMetadata(sourceMetadata: Record<string,
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
const candidate = raw as Record<string, unknown>;
if (typeof candidate.groupId !== "string" || !candidate.groupId.trim()) return undefined;
if (candidate.source !== "planning" && candidate.source !== "mission") return undefined;
if (candidate.source !== "planning" && candidate.source !== "mission" && candidate.source !== "new-task") return undefined;
if (candidate.assignmentMode !== "shared" && candidate.assignmentMode !== "per-task-derived") return undefined;
const inheritedBaseBranch = typeof candidate.inheritedBaseBranch === "string" && candidate.inheritedBaseBranch.trim().length > 0
? candidate.inheritedBaseBranch.trim()
@@ -222,7 +222,7 @@ function withTaskBranchContextInSourceMetadata(
interface BranchGroupRow {
id: string;
sourceType: "mission" | "planning";
sourceType: "mission" | "planning" | "new-task";
sourceId: string;
branchName: string;
worktreePath: string | null;
@@ -4345,6 +4345,11 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
return row ? this.rowToBranchGroup(row) : null;
}
getBranchGroupByBranchName(branchName: string): BranchGroup | null {
const row = this.db.prepare(`SELECT * FROM branch_groups WHERE branchName = ? AND status = 'open' ORDER BY createdAt DESC LIMIT 1`).get(branchName) as BranchGroupRow | undefined;
return row ? this.rowToBranchGroup(row) : null;
}
ensureBranchGroupForSource(
sourceType: BranchGroup["sourceType"],
sourceId: string,

View File

@@ -1676,7 +1676,7 @@ export interface TaskSource {
sourceMetadata?: Record<string, unknown>;
}
export type TaskBranchGroupSource = "planning" | "mission";
export type TaskBranchGroupSource = "planning" | "mission" | "new-task";
export type TaskBranchAssignmentMode = "shared" | "per-task-derived";

View File

@@ -146,7 +146,7 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
const githubRepoOverrideTrimmed = githubRepoOverride.trim();
const githubRepoOverrideInvalid = githubRepoOverrideTrimmed.length > 0 && !REPO_OVERRIDE_RE.test(githubRepoOverrideTrimmed);
const isBranchNameRequired = branchMode === "existing" || branchMode === "custom-new";
const isBranchNameRequired = branchMode === "existing" || branchMode === "custom-new" || branchMode === "shared-group";
const hasInvalidBranchSelection = isBranchNameRequired && !branch.trim();
// Track dirty state

View File

@@ -56,7 +56,7 @@ export interface PendingImage {
}
type TaskExecutionModeSelection = "standard" | "fast";
export type BranchSelectionMode = "project-default" | "auto-new" | "existing" | "custom-new";
export type BranchSelectionMode = "project-default" | "auto-new" | "existing" | "custom-new" | "shared-group";
export interface TaskFormProps {
mode: "create" | "edit";
@@ -1029,20 +1029,21 @@ export function TaskForm({
<option value="auto-new">Create auto-named branch per task</option>
<option value="existing">Use existing branch</option>
<option value="custom-new">Create custom new branch</option>
<option value="shared-group">Merge into a shared feature branch</option>
</select>
</>
) : null}
{onBranchChange && (!onBranchModeChange || branchMode === "existing" || branchMode === "custom-new") && (
{onBranchChange && (!onBranchModeChange || branchMode === "existing" || branchMode === "custom-new" || branchMode === "shared-group") && (
<>
<label htmlFor="task-working-branch" className="model-select-label">
{onBranchModeChange ? "Branch name" : "Working branch"}
{branchMode === "shared-group" ? "Shared feature branch" : (onBranchModeChange ? "Branch name" : "Working branch")}
</label>
<input
id="task-working-branch"
className="input"
value={branch || ""}
onChange={(e) => onBranchChange(e.target.value)}
placeholder="e.g. feature/my-task"
placeholder={branchMode === "shared-group" ? "e.g. clionboarding" : "e.g. feature/my-task"}
disabled={disabled}
/>
</>

View File

@@ -313,6 +313,43 @@ describe("NewTaskModal", () => {
});
});
it("requires branch name for shared-group mode", async () => {
const { props } = renderNewTaskModal();
fireEvent.change(screen.getByPlaceholderText("What needs to be done?"), { target: { value: "Task with shared group" } });
fireEvent.click(screen.getByTestId("task-form-more-options-toggle"));
fireEvent.change(screen.getByLabelText("Branch strategy"), { target: { value: "shared-group" } });
expect(screen.getByRole("button", { name: "Create Task" })).toBeDisabled();
expect(screen.getByText("Branch name is required for this branch strategy.")).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
expect(props.onCreateTask).not.toHaveBeenCalled();
});
});
it("submits shared-group branch selection when shared branch exists", async () => {
const { props } = renderNewTaskModal();
fireEvent.change(screen.getByPlaceholderText("What needs to be done?"), { target: { value: "Task with shared group" } });
fireEvent.click(screen.getByTestId("task-form-more-options-toggle"));
fireEvent.change(screen.getByLabelText("Branch strategy"), { target: { value: "shared-group" } });
fireEvent.change(screen.getByLabelText("Shared feature branch"), { target: { value: " feature/shared " } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
expect(props.onCreateTask).toHaveBeenCalledWith(
expect.objectContaining({
branchSelection: {
mode: "shared-group",
branchName: "feature/shared",
},
}),
);
});
});
it("still submits when setup warnings are shown", async () => {
const { fetchAuthStatus } = await import("../../api");
vi.mocked(fetchAuthStatus).mockResolvedValueOnce({

View File

@@ -26,6 +26,17 @@ describe("branch-selection", () => {
);
});
it("resolves shared-group with shared feature branch and no working branch", () => {
expect(resolveBranchSelection({ mode: "shared-group", branchName: "feature/shared" }, undefined, "main")).toEqual({
branch: undefined,
baseBranch: undefined,
sharedFeatureBranch: "feature/shared",
});
expect(() => resolveBranchSelection({ mode: "shared-group" }, undefined, undefined)).toThrow(
"branchSelection.branchName is required for shared-group mode",
);
});
it("resolves assignment context", () => {
expect(resolveBranchAssignmentContext(undefined)).toEqual({ mode: "shared" });
expect(resolveBranchAssignmentContext({ mode: "per-task-derived" })).toEqual({ mode: "per-task-derived" });
@@ -46,6 +57,7 @@ describe("branch-selection", () => {
it("reads requested branch mode", () => {
expect(getBranchSelectionMode(undefined)).toBeUndefined();
expect(getBranchSelectionMode({ mode: "auto-new" })).toBe("auto-new");
expect(getBranchSelectionMode({ mode: "shared-group" })).toBe("shared-group");
});
it("re-exports entry-point branch assignment helper", () => {

View File

@@ -41,7 +41,7 @@ import * as projectStoreResolver from "../project-store-resolver.js";
// Mock MissionStore factory
function createMockMissionStore(options?: {
ensureBranchGroupForSource?: (sourceType: "planning" | "mission", sourceId: string, init: { branchName: string; autoMerge?: boolean }) => unknown;
ensureBranchGroupForSource?: (sourceType: "planning" | "mission" | "new-task", sourceId: string, init: { branchName: string; autoMerge?: boolean }) => unknown;
settingsAutoMerge?: boolean;
persistTask?: (task: { id: string; branch?: string; baseBranch?: string }) => void;
}) {
@@ -716,13 +716,13 @@ function createMockStore(): TaskStore {
const tasks = new Map<string, { id: string; branch?: string; baseBranch?: string }>();
const branchGroups = new Map<string, {
id: string;
sourceType: "planning" | "mission";
sourceType: "planning" | "mission" | "new-task";
sourceId: string;
branchName: string;
autoMerge: boolean;
}>();
const ensureBranchGroupForSource = vi.fn((sourceType: "planning" | "mission", sourceId: string, init: { branchName: string; autoMerge?: boolean }) => {
const ensureBranchGroupForSource = vi.fn((sourceType: "planning" | "mission" | "new-task", sourceId: string, init: { branchName: string; autoMerge?: boolean }) => {
const key = `${sourceType}:${sourceId}`;
const existing = branchGroups.get(key);
if (existing) return existing;
@@ -737,7 +737,7 @@ function createMockStore(): TaskStore {
return created;
});
const getBranchGroupBySource = vi.fn((sourceType: "planning" | "mission", sourceId: string) =>
const getBranchGroupBySource = vi.fn((sourceType: "planning" | "mission" | "new-task", sourceId: string) =>
branchGroups.get(`${sourceType}:${sourceId}`) ?? null,
);

View File

@@ -178,7 +178,7 @@ function createMockGlobalSettingsStore() {
function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
const branchGroups = new Map<string, {
id: string;
sourceType: "planning" | "mission";
sourceType: "planning" | "mission" | "new-task";
sourceId: string;
branchName: string;
autoMerge: boolean;
@@ -223,7 +223,7 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
updatePrInfo: vi.fn().mockResolvedValue(undefined),
updateIssueInfo: vi.fn().mockResolvedValue(undefined),
getRootDir: vi.fn().mockReturnValue("/fake/root"),
ensureBranchGroupForSource: vi.fn(function (this: TaskStore, sourceType: "planning" | "mission", sourceId: string, init: { branchName: string; autoMerge?: boolean }) {
ensureBranchGroupForSource: vi.fn(function (this: TaskStore, sourceType: "planning" | "mission" | "new-task", sourceId: string, init: { branchName: string; autoMerge?: boolean }) {
const existing = this.getBranchGroupBySource(sourceType, sourceId);
if (existing) {
return existing;
@@ -242,7 +242,7 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
branchGroups.set(`${sourceType}:${sourceId}`, created);
return created;
}),
getBranchGroupBySource: vi.fn((sourceType: "planning" | "mission", sourceId: string) =>
getBranchGroupBySource: vi.fn((sourceType: "planning" | "mission" | "new-task", sourceId: string) =>
branchGroups.get(`${sourceType}:${sourceId}`) ?? null
),
listWorkflowSteps: vi.fn().mockResolvedValue([]),

View File

@@ -183,6 +183,9 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
createTaskWithReservedId: undefined,
moveTask: vi.fn(),
updateTask: vi.fn(),
getBranchGroupByBranchName: vi.fn(),
ensureBranchGroupForSource: vi.fn(),
setTaskBranchGroup: vi.fn().mockResolvedValue(undefined),
deleteTask: vi.fn(),
mergeTask: vi.fn(),
archiveTask: vi.fn(),
@@ -975,6 +978,105 @@ describe("POST /tasks", () => {
expect(res.body.branch).toBe("fusion/fn-5671-branch-strategy-dropdown");
});
it("persists per-task branch and shared group context for shared-group branchSelection", async () => {
const createdTask = {
...FAKE_TASK_DETAIL,
id: "FN-7001",
title: "Shared Group Branch Task",
description: "Task with shared branch target",
column: "triage",
branch: undefined,
};
const ensuredGroup = {
id: "BG-001",
sourceType: "new-task" as const,
sourceId: "feature/shared",
branchName: "feature/shared",
autoMerge: false,
prState: "none" as const,
status: "open" as const,
createdAt: Date.now(),
updatedAt: Date.now(),
};
(store.createTask as ReturnType<typeof vi.fn>).mockResolvedValue(createdTask);
(store.getBranchGroupByBranchName as ReturnType<typeof vi.fn>).mockReturnValue(null);
(store.ensureBranchGroupForSource as ReturnType<typeof vi.fn>).mockReturnValue(ensuredGroup);
(store.updateTask as ReturnType<typeof vi.fn>).mockResolvedValue({
...createdTask,
branch: "feature/shared/shared-group-branch-task",
branchContext: { groupId: "BG-001", source: "new-task", assignmentMode: "shared" },
});
const res = await REQUEST(
buildApp(),
"POST",
"/api/tasks",
JSON.stringify({
description: "Task with shared branch target",
title: "Shared Group Branch Task",
branchSelection: {
mode: "shared-group",
branchName: "feature/shared",
},
}),
{ "Content-Type": "application/json" },
);
expect(res.status).toBe(201);
expect(store.ensureBranchGroupForSource).toHaveBeenCalledWith("new-task", "feature/shared", { branchName: "feature/shared" });
expect(store.setTaskBranchGroup).toHaveBeenCalledWith("FN-7001", "BG-001");
expect(store.updateTask).toHaveBeenCalledWith("FN-7001", {
branch: "feature/shared/shared-group-branch-task",
});
expect(res.body.branch).toBe("feature/shared/shared-group-branch-task");
});
it("joins existing open branch groups by shared branch name", async () => {
const createdTask = {
...FAKE_TASK_DETAIL,
id: "FN-7002",
title: "Join Existing Group",
description: "Task joins existing group",
column: "triage",
branch: undefined,
};
const existingGroup = {
id: "BG-existing",
sourceType: "planning" as const,
sourceId: "PS-1",
branchName: "feature/shared",
autoMerge: false,
prState: "none" as const,
status: "open" as const,
createdAt: Date.now(),
updatedAt: Date.now(),
};
(store.createTask as ReturnType<typeof vi.fn>).mockResolvedValue(createdTask);
(store.getBranchGroupByBranchName as ReturnType<typeof vi.fn>).mockReturnValue(existingGroup);
(store.updateTask as ReturnType<typeof vi.fn>).mockResolvedValue({
...createdTask,
branch: "feature/shared/join-existing-group",
branchContext: { groupId: "BG-existing", source: "planning", assignmentMode: "shared" },
});
const res = await REQUEST(
buildApp(),
"POST",
"/api/tasks",
JSON.stringify({
description: "Task joins existing group",
title: "Join Existing Group",
branchSelection: { mode: "shared-group", branchName: "feature/shared" },
}),
{ "Content-Type": "application/json" },
);
expect(res.status).toBe(201);
expect(store.ensureBranchGroupForSource).not.toHaveBeenCalled();
expect(store.setTaskBranchGroup).toHaveBeenCalledWith("FN-7002", "BG-existing");
expect(res.body.branch).not.toBe("feature/shared");
});
it("returns 400 when create branch payload is not a string", async () => {
const res = await REQUEST(
buildApp(),

View File

@@ -24,7 +24,8 @@ export type BranchSelectionMode =
| "project-default"
| "auto-new"
| "existing"
| "custom-new";
| "custom-new"
| "shared-group";
export interface BranchSelectionPayload {
mode?: unknown;
@@ -47,8 +48,9 @@ export function getBranchSelectionMode(selectionInput: unknown): BranchSelection
"auto-new",
"existing",
"custom-new",
"shared-group",
].includes(mode)) {
throw badRequest("branchSelection.mode must be one of: project-default, auto-new, existing, custom-new");
throw badRequest("branchSelection.mode must be one of: project-default, auto-new, existing, custom-new, shared-group");
}
return mode as BranchSelectionMode;
}
@@ -58,6 +60,7 @@ export type PlanningBranchMode = "shared" | "per-task-derived";
export interface ResolvedBranchSelection {
branch?: string;
baseBranch?: string;
sharedFeatureBranch?: string;
}
export interface BranchAssignmentContext {
@@ -106,6 +109,18 @@ export function resolveBranchSelection(
}
const branchName = normalizeOptionalBranch(selection.branchName, "branchSelection.branchName");
if (mode === "shared-group") {
if (!branchName) {
throw badRequest("branchSelection.branchName is required for shared-group mode");
}
return {
branch: undefined,
baseBranch,
sharedFeatureBranch: branchName,
};
}
if (!branchName) {
throw badRequest("branchSelection.branchName is required for existing/custom-new modes");
}

View File

@@ -44,7 +44,7 @@ import { planTaskWorktreePath } from "@fusion/engine";
import type { RunAuditEventInput } from "@fusion/core";
import { ApiError, badRequest, conflict, notFound } from "../api-error.js";
import type { ApiRoutesContext } from "./types.js";
import { deriveAutoTaskBranch, getBranchSelectionMode, resolveBranchSelection } from "./branch-selection.js";
import { deriveAutoTaskBranch, derivePerTaskBranch, getBranchSelectionMode, resolveBranchSelection } from "./branch-selection.js";
const REVIEW_BLOCK_RE = /##\s+(Code|Plan)\s+Review:[\s\S]*?(?=\n##\s+(?:Code|Plan)\s+Review:|$)/gi;
const REVIEW_VERDICT_RE = /###\s+Verdict:\s*(APPROVE|REVISE|RETHINK|UNAVAILABLE)\b/i;
@@ -952,7 +952,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
: { sourceType: "api" as const };
const requestedBranchMode = getBranchSelectionMode(branchSelection);
const { branch: normalizedBranch, baseBranch: normalizedBaseBranch } =
const { branch: normalizedBranch, baseBranch: normalizedBaseBranch, sharedFeatureBranch } =
resolveBranchSelection(branchSelection, branch, baseBranch);
let validatedGithubTracking: { enabled?: boolean; repoOverride?: string } | undefined;
@@ -1246,8 +1246,19 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
})
: task;
const taskWithBranchContext = requestedBranchMode === "shared-group" && sharedFeatureBranch
? await (async () => {
const group = scopedStore.getBranchGroupByBranchName(sharedFeatureBranch)
?? scopedStore.ensureBranchGroupForSource("new-task", sharedFeatureBranch, { branchName: sharedFeatureBranch });
await scopedStore.setTaskBranchGroup(taskWithAutoBranch.id, group.id);
const taskSegment = ((taskWithAutoBranch.title ?? "").trim() || taskWithAutoBranch.description).slice(0, 60);
const workingBranch = derivePerTaskBranch(sharedFeatureBranch, taskSegment);
return scopedStore.updateTask(taskWithAutoBranch.id, { branch: workingBranch });
})()
: taskWithAutoBranch;
const deterministicReconcile = await reconcileDeterministicDuplicate(scopedStore, {
createdTask: taskWithAutoBranch,
createdTask: taskWithBranchContext,
fingerprint: bypassDuplicateCheck === true ? null : contentFingerprint,
windowMs: 60_000,
logger: runtimeLogger,
@@ -1261,8 +1272,8 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
try {
await scopedStore.recordActivity({
type: "task:duplicate-warning-overridden",
taskId: taskWithAutoBranch.id,
taskTitle: taskWithAutoBranch.title,
taskId: taskWithBranchContext.id,
taskTitle: taskWithBranchContext.title,
details: `Created despite ${acknowledgedDuplicateIds.length} possible duplicate(s): ${acknowledgedDuplicateIds.join(", ")}`,
metadata: {
acknowledgedDuplicateIds,
@@ -1277,7 +1288,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
}
}
res.status(201).json(taskWithAutoBranch);
res.status(201).json(taskWithBranchContext);
return;
} finally {
deterministicGuard.releaseLock();