Skip to content

Bug: error in finally during halt propagates despite being caught#1128

Open
taras wants to merge 1 commit intothefrontside:v4from
taras:bug/halt-finally-error-double-propagation
Open

Bug: error in finally during halt propagates despite being caught#1128
taras wants to merge 1 commit intothefrontside:v4from
taras:bug/halt-finally-error-double-propagation

Conversation

@taras
Copy link
Member

@taras taras commented Feb 26, 2026

Motivation

When a child task throws in a finally block during halt, the error propagates through two independent paths:

  1. Through halt() return — the caller's try/catch catches it ✓
  2. Through the scope tree — fails the parent scope unconditionally ✗

This means even when the caller explicitly handles the error from yield* task.halt(), the parent scope is still considered failed and the error escapes.

let result = run(function* () {
  let task = yield* spawn(function* () {
    try {
      yield* suspend();
    } finally {
      throw new Error("finally-boom");
    }
  });

  yield* sleep(0);

  try {
    yield* task.halt();
  } catch (error) {
    // Error IS caught here ✓
  }

  return "success"; // This executes ✓
});

// But the run() rejects with "finally-boom" instead of resolving with "success" ✗
await expect(result).resolves.toEqual("success");

All three variants reproduce: synchronous throw, synchronous action reject, and async until(Promise.reject(...)).

Approach

This PR adds a single failing test to test/spawn.test.ts that demonstrates the bug. No fix is included — this is intended to document the issue and serve as a regression test for a future fix.

When a child task throws in a finally block during halt, the error
propagates through two independent paths: (1) through halt() return,
which the caller can catch, and (2) through the scope tree, which
fails the parent scope unconditionally. This means even when the
caller explicitly catches the error from yield* task.halt(), the
parent scope is still considered failed.

This test demonstrates the bug by spawning a child that throws in
finally during halt, catching the error via try/catch around
yield* task.halt(), and asserting the parent resolves with 'success'.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 26, 2026

Open in StackBlitz

npm i https://pkg.pr.new/thefrontside/effection@1128

commit: 661ea3f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant