diff --git a/.changeset/fn-8778-planning-lock-receiver.md b/.changeset/fn-8778-planning-lock-receiver.md new file mode 100644 index 0000000000..8bde094ee4 --- /dev/null +++ b/.changeset/fn-8778-planning-lock-receiver.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Prevent planning finalization from crashing after PROMPT.md writes. +category: fix +dev: Preserves the TaskStore receiver when invoking the planning lifecycle lock. diff --git a/packages/engine/src/__tests__/triage.test.ts b/packages/engine/src/__tests__/triage.test.ts index 9619445f38..fcdfb156ee 100644 --- a/packages/engine/src/__tests__/triage.test.ts +++ b/packages/engine/src/__tests__/triage.test.ts @@ -3278,9 +3278,23 @@ describe("specified triage recovery", () => { it("fences a stale finalizer after dependency re-seed commits before lifecycle-lock acquisition", async () => { const stalePlannerSnapshot = createTriageTask({ status: "planning" }); const store = createMockStore({ - withPlanningLifecycleLock: vi.fn(async (_id, callback) => callback()), getTask: vi.fn().mockResolvedValue({ ...stalePlannerSnapshot, status: "needs-replan" }), }); + /* + * FNXC:PlanningDependencyReseed 2026-08-04-08:04: + * Finalization may feature-detect the lifecycle method, but it must invoke + * that extracted method with TaskStore as its receiver. The production + * method reads instance state (`asyncLayer`); an arrow-function mock hid the + * unbound-call crash that stranded FN-8778 after every successful prompt write. + */ + store.withPlanningLifecycleLock = vi.fn(async function ( + this: TaskStore, + _id: string, + callback: () => Promise, + ) { + expect(this).toBe(store); + return await callback(); + }) as typeof store.withPlanningLifecycleLock; const processor = new TriageProcessor(store, rootDir); await (processor as unknown as { diff --git a/packages/engine/src/triage.ts b/packages/engine/src/triage.ts index 5f2fdaa9fa..23026ee887 100644 --- a/packages/engine/src/triage.ts +++ b/packages/engine/src/triage.ts @@ -4138,7 +4138,7 @@ export class TriageProcessor { */ try { return lifecycleLock - ? await lifecycleLock(task.id, finalizeUnderLock) + ? await this.store.withPlanningLifecycleLock(task.id, finalizeUnderLock) : await finalizeUnderLock(); } finally { this.finalizing.delete(task.id);