diff --git a/packages/opencode/src/dag/runtime/recovery.ts b/packages/opencode/src/dag/runtime/recovery.ts index 799234d50a..a9bbc0848a 100644 --- a/packages/opencode/src/dag/runtime/recovery.ts +++ b/packages/opencode/src/dag/runtime/recovery.ts @@ -65,7 +65,7 @@ export function reconcileWorkflow( // never revisit it if the workflow is about to become terminal. if (node.status === "pending" || node.status === "queued") { if (node.childSessionId && cancelSession) { - yield* cancelSession(node.childSessionId).pipe(Effect.catch(() => Effect.void)) + yield* cancelSession(node.childSessionId) } continue } diff --git a/packages/opencode/test/dag/dag-recovery.test.ts b/packages/opencode/test/dag/dag-recovery.test.ts index e1537d604e..9a6ae584e4 100644 --- a/packages/opencode/test/dag/dag-recovery.test.ts +++ b/packages/opencode/test/dag/dag-recovery.test.ts @@ -171,6 +171,24 @@ describe("reconcileWorkflow", () => { expect(result).toEqual({ reconciled: 0, ownershipLost: 0 }) }) + it("aborts recovery when a stale restart-orphan session cannot be cancelled", async () => { + const events: TrackedEvent[] = [] + const nodes = [makeNodeRow({ id: "n1", status: "queued", childSessionId: "ses_stale" })] + const dagLayer = makeDagLayer(nodes, events) + const checkStatus = () => Effect.succeed("active" as const) + const cancelSession = () => Effect.fail(new Error("cancel unavailable")) + + const exit = await Effect.runPromise( + reconcileWorkflow("wf-1", checkStatus, cancelSession).pipe( + Effect.provide(dagLayer), + Effect.exit, + ), + ) + + expect(Exit.isFailure(exit)).toBe(true) + expect(events).toEqual([]) + }) + it("cancels and fails a zero-message child classified as unknown exactly once", async () => { const events: TrackedEvent[] = [] const cancelled: string[] = []