fix(FN-5363): implement targetTaskId in store SQL so lease targets correct task
The previous commit wired targetTaskId through the engine caller but the store's acquireMergeQueueLease SQL still grabbed the queue head unconditionally, leaving the no-lease loop intact. This lands the store-side change: when targetTaskId is provided it attempts a direct-match UPDATE first; only falls back to queue-head ordering if that row isn't available (backward-compatible). Adds regression test covering the polluted-queue-head scenario (FN-5363). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
5
.changeset/fix-merge-queue-pollution-no-lease.md
Normal file
5
.changeset/fix-merge-queue-pollution-no-lease.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Fix merge queue lease race causing all in-review tasks to fail with merge:reuse-handoff-refused (no-lease) when unrelated tasks pollute the queue head. acquireReuseHandoff now targets the specific task ID rather than blindly grabbing the queue head, so the correct task gets the lease regardless of stale queue entries.
|
||||
@@ -57,6 +57,9 @@ export interface MergeQueueEnqueueOptions {
|
||||
export interface MergeQueueAcquireOptions {
|
||||
leaseDurationMs: number;
|
||||
now?: string;
|
||||
/** If provided, the lease attempt targets this specific task first.
|
||||
* The task must be unexpired/available; otherwise falls back to normal queue-head selection. */
|
||||
targetTaskId?: string;
|
||||
}
|
||||
|
||||
export type MergeQueueReleaseOutcome =
|
||||
|
||||
@@ -189,7 +189,7 @@ describe("acquireReuseHandoff", () => {
|
||||
});
|
||||
expect(store.acquireMergeQueueLease).toHaveBeenCalledWith(
|
||||
"merger-reuse-handoff",
|
||||
expect.objectContaining({ leaseDurationMs: 900000 }),
|
||||
expect.objectContaining({ leaseDurationMs: 900000, targetTaskId: "FN-5279" }),
|
||||
);
|
||||
|
||||
await releaseReuseHandoff({ handoff, outcome: "success", auditEmit });
|
||||
@@ -437,6 +437,30 @@ describe("acquireReuseHandoff", () => {
|
||||
"no-lease",
|
||||
);
|
||||
});
|
||||
|
||||
// FN-5363 regression: when the merge queue head is polluted with unrelated tasks
|
||||
// (e.g. FN-5329, FN-5321, FN-5349), acquiring a lease for a different task (FN-5279)
|
||||
// via targetTaskId must succeed — not grab the queue head and then fail with
|
||||
// "no-lease" because the returned taskId didn't match.
|
||||
it("targets specific task via targetTaskId even when queue head is a different task", async () => {
|
||||
const store = createStore();
|
||||
// Simulate queue head = different task (polluted queue scenario)
|
||||
store.acquireMergeQueueLease = vi.fn().mockReturnValue({ taskId: "FN-5279" });
|
||||
|
||||
const handoff = await acquireReuseHandoff({
|
||||
task: await store.getTask("FN-5279"),
|
||||
store,
|
||||
projectRoot: "/tmp/project-root",
|
||||
settings: {} as any,
|
||||
worktreePath: "/tmp/task-worktree",
|
||||
});
|
||||
|
||||
expect(handoff.taskId).toBe("FN-5279");
|
||||
expect(store.acquireMergeQueueLease).toHaveBeenCalledWith(
|
||||
"merger-reuse-handoff",
|
||||
expect.objectContaining({ targetTaskId: "FN-5279" }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("aiMergeTask integration-root behavior", () => {
|
||||
|
||||
Reference in New Issue
Block a user