Skip to content

JSC: propagate async context through PromiseFinallyAwaitJob and PromiseResolveWithoutHandlerJob - #268

Open
robobun wants to merge 2 commits into
mainfrom
robobun/async-function-resume-context-ordering
Open

JSC: propagate async context through PromiseFinallyAwaitJob and PromiseResolveWithoutHandlerJob#268
robobun wants to merge 2 commits into
mainfrom
robobun/async-function-resume-context-ordering

Conversation

@robobun

@robobun robobun commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Problem

Two internal microtasks settle a promise without the async context of the call that set them up, so in Bun the rejection tracker (and with it process.on("unhandledRejection"), oven-sh/bun#31721) sees no AsyncLocalStorage store for them. Node reports the store in both cases.

  1. PromiseFinallyAwaitJob. When a .finally() callback returns a thenable, the follow-up that propagates the original settlement (or rejects with the thenable's reason) runs as this job. Unlike PromiseFinallyReactionJob a few lines above it, the case did not install the async context, and phase 1 did not capture it on the reaction it schedules.
const als = new AsyncLocalStorage();
process.on("unhandledRejection", () => console.log(als.getStore()));
als.run("ctx", () => Promise.resolve().finally(() => Promise.reject(new Error("e"))));
// bun: undefined   node: "ctx"
  1. PromiseResolveWithoutHandlerJob. A reaction registered with then() captures the async context, but when the settled side has no handler the derived promise is settled by this job, and every path that queues it dropped the context: triggerPromiseReactions for a JSFullPromiseReaction with an undefined side, the already-settled arms of performPromiseThen and performPromiseThenWithContext, and the PromiseResolveThenableJobFast reaction that makes one promise adopt another. The same shape with a handler, or through the non-fast thenable job, kept the context.
als.run("ctx", () => { Promise.reject(new Error("e")).then(() => {}); });
als.run("ctx", () => { new Promise(resolve => resolve(rejectedElsewhere)); });
als.run("ctx", async () => rejectedElsewhere);
// bun: undefined for the derived promise in all three   node: "ctx"

This is what makes Next.js 16 log NEXT_PRERENDER_INTERRUPTED rejections under Bun that it suppresses under Node (oven-sh/bun#39847): its unhandledRejection filter reads the prerender store, and the rejections it has to filter are exactly these handler-less derived promises.

Fix

  • Phase 1 of .finally() captures the active async context alongside the reaction at its two schedule points (performPromiseThenWithInternalMicrotask and createResolvingFunctionsWithInternalMicrotask), and the PromiseFinallyAwaitJob case installs and restores it, mirroring PromiseFinallyReactionJob. The synchronous promiseFinallyAwaitJob calls stay inside the phase 1 window and are unaffected.
  • Every site that queues PromiseResolveWithoutHandlerJob passes the reaction's context as the job's third argument (it was jsUndefined()), and the case installs it around the settle with AsyncContextSwapScope, the way PromiseReactionJob does. PromiseResolveThenableJobFast installs the captured context before the species check, so its slow path (performPromiseThen) captures the same context, and captures it on the adopting reaction with wrapWithCurrent.
  • Without an active context every new value is undefined, so the paths are unchanged in that case. Interpreter::getAsyncStackTrace already unwraps the tuple shape the reactions now carry.

Context

The AsyncFunctionResume settle ordering fix that this PR originally carried landed separately as #295.

Audit

Internal microtask cases that settle a promise while a Bun async context can be active:

Case State
PromiseReactionJob already correct (settle, then restore)
AsyncFunctionResume fixed by #295
AsyncGeneratorYieldAwaited, AsyncGeneratorBodyCallNormal/Return, AsyncGeneratorAwaitReturnContinuation already correct
PromiseResolveThenableJob, PromiseResolveThenableJobWithInternalMicrotask already correct (install, then register)
PromiseFinallyReactionJob (phase 1) already correct
PromiseFinallyAwaitJob (phase 2) this PR
PromiseResolveWithoutHandlerJob, and PromiseResolveThenableJobFast which registers it this PR
Inline reactions (settleInlineHandler) only created when no context is active, nothing to carry

Verification

Consumed by oven-sh/bun#31721. Two of its fixtures run against both Bun and Node and need this change: async-context-unhandled-rejection-finally-thenable.js (case 1) and async-context-unhandled-rejection-then-passthrough.js (four legs of case 2: pending source, settled source, adoption through resolve(), adoption through an async function's return). Both fail on Bun without this change and pass with it. The other async shapes (throw in finally, async generators, await then throw) pass with or without it. Measured with the Next.js app from oven-sh/bun#39847 (3 requests): release bun logs 6 unhandledRejection, bun with only the bun-side change still logs 6, bun on this PR's preview (autobuild-preview-pr-268-33161987) logs 0, the same as node.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any issues — the reordering cleanly mirrors the existing PromiseReactionJob pattern — but since this changes observable async-context semantics on the async-function settle path, it's worth a quick human sanity check.

Extended reasoning...

Overview

This PR reorders two statements in each of the two terminal arms of InternalMicrotask::AsyncFunctionResume in JSMicrotask.cpp: it now calls promise->reject() / promise->resolve() before restoring the Bun async-context slot, instead of after. The net diff is moving scope.release(); promise->reject/resolve(...) above the asyncContextData->putInternalField(vm, 0, restoreAsyncContext) restore in both branches, plus a comment explaining the ordering.

Correctness

The new ordering is a direct copy of the already-established pattern in PromiseReactionJob a few dozen lines above (lines ~1855–1866), which explicitly documents the same invariant. asyncContextData is rooted via globalObject->m_asyncContextData and restoreAsyncContext is a stack JSValue, so there's no GC hazard from calling into reject()/resolve() before the restore. putInternalField doesn't need the throw scope, so scope.release() preceding it is fine — again identical to the reference pattern. The audit table in the PR description matches what I see in the file.

Security risks

None. This is a Bun-only (USE(BUN_JSC_ADDITIONS)) reordering of when an async-context slot is restored; no new inputs, no parsing, no auth/crypto/permissions surface.

Level of scrutiny

Medium-high. The diff is mechanically tiny and pattern-matched against existing code, and the bug-hunting pass found nothing. However, it lives on the hot path of async function completion in JavaScriptCore and intentionally changes what the embedder's promiseRejectionTracker (and thenable-resolution jobs) observe. That's a deliberate, user-visible semantic change to AsyncLocalStorage behavior across await, so I'd rather a human confirm the intended semantics than auto-approve.

Other factors

Verification is via the downstream oven-sh/bun#31721 fixture suite rather than an in-tree JSC test, and there are no prior reviewer comments on the PR. Given it's engine-runtime behavior rather than a config/typo change, deferring.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 23 days. After that, they cost $0.25 per reviewed file.

Or wait 41 minutes for your next included review.

View limit details

Limit details: You’ve used all 5 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a3a1ab0b-f6ed-4137-af91-139b69491978

📥 Commits

Reviewing files that changed from the base of the PR and between c4ddc0c and b1fbd2f.

📒 Files selected for processing (2)
  • Source/JavaScriptCore/runtime/JSMicrotask.cpp
  • Source/JavaScriptCore/runtime/JSPromise.cpp

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c828cdaf-37b6-4e56-9a73-df4b2bf1216e

📥 Commits

Reviewing files that changed from the base of the PR and between f5deafe and da719fa.

📒 Files selected for processing (2)
  • Source/JavaScriptCore/runtime/JSMicrotask.cpp
  • Source/JavaScriptCore/runtime/JSPromise.cpp

Included review availability: Your plan provides up to 5 included reviews per hour; 0 remain after this review.


Walkthrough

Changes

Bun async contexts now propagate through promise reactions, thenable resolution, promise settlement, and finally await microtasks. Contexts are captured, passed to adoption jobs, unwrapped when tuple-encoded, and restored before processing.

Promise async context propagation

Layer / File(s) Summary
Propagate context through reaction adoption
Source/JavaScriptCore/runtime/JSPromise.cpp
Promise reaction paths pass captured contexts to fulfillment and rejection adoption microtasks, including reactions without handlers.
Restore context during thenable settlement
Source/JavaScriptCore/runtime/JSMicrotask.cpp
Thenable resolution restores the captured context before species handling and passes it through settlement processing.
Propagate context through finally await jobs
Source/JavaScriptCore/runtime/JSMicrotask.cpp
Promise finally scheduling retains the captured context, and PromiseFinallyAwaitJob unwraps and restores it before processing the reaction.

Merge Risk: ⚪ Minimal · up to da719

The change propagates async context through promise settlement paths to align rejection handling with Node behavior; no actionable merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: async context propagation through PromiseFinallyAwaitJob and PromiseResolveWithoutHandlerJob.
Description check ✅ Passed The description is detailed and directly explains the problem, affected microtasks, implementation, related bugs, audit scope, and verification results. It does not include every legacy template field…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description is detailed and directly explains the problem, affected microtasks, implementation, related bugs, audit scope, and verification results. It does not include every legacy template field, such as a Bugzilla URL, reviewer line, or explicit file/function list, but the essential technical information is complete.

Warning

Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use path_filters to narrow the review scope.


Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — small reorder that mirrors the established PromiseReactionJob pattern in the same file.

Extended reasoning...

Overview

This PR reorders two statements in each of the two terminal arms of InternalMicrotask::AsyncFunctionResume in Source/JavaScriptCore/runtime/JSMicrotask.cpp. Previously the async context was restored before calling promise->reject() / promise->resolve(); now the settle happens first and the context is restored afterwards. A comment is added referencing the identical ordering already used by PromiseReactionJob at line ~1855 in the same file.

Security risks

None. The change is a pure statement reorder inside #if USE(BUN_JSC_ADDITIONS) guards. putInternalField is a simple internal-field store with a write barrier and does not depend on the exception scope, so moving it after scope.release() and the settle call is safe — this is exactly what PromiseReactionJob already does a few dozen lines above (verified at JSMicrotask.cpp:1858-1866). No new user-controlled data flow, no auth/crypto/permissions surface.

Level of scrutiny

Low-to-moderate. While this is core JSC async/await runtime code, the change is mechanical: it aligns two branches with an existing, documented, in-tree pattern rather than introducing new logic. The PR description includes a thorough audit of every putInternalField(vm, 0, restoreAsyncContext) site in the file confirming the other branches already follow the settle-then-restore ordering. The behavioural delta is narrow (what promiseRejectionTracker and thenable-resolution jobs observe as the current async context), and .then()/.catch() handler semantics are unaffected since performPromiseThen captures context at registration time.

Other factors

The change is Bun-specific (entirely inside USE(BUN_JSC_ADDITIONS)), is consumed and tested by oven-sh/bun#31721 with fixtures that fail before and pass after, and the multi-agent bug hunter found no issues. No prior human or bot review comments to address. Net diff is ~10 lines of reordering plus a 4-line explanatory comment.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

Preview Builds

Commit Release Date
b1fbd2f1 autobuild-preview-pr-268-b1fbd2f1 2026-08-28 09:16:51 UTC
da719fa3 autobuild-preview-pr-268-da719fa3 2026-08-28 02:24:15 UTC
34265704 autobuild-preview-pr-268-34265704 2026-08-28 01:44:44 UTC
21ac59a3 autobuild-preview-pr-268-21ac59a3 2026-08-27 19:15:48 UTC
58a3181d autobuild-preview-pr-268-58a3181d 2026-08-26 22:53:34 UTC
4c71e983 autobuild-preview-pr-268-4c71e983 2026-08-26 06:36:50 UTC
4b44c711 autobuild-preview-pr-268-4b44c711 2026-08-25 22:43:08 UTC
57914715 autobuild-preview-pr-268-57914715 2026-08-25 13:09:17 UTC
ca208900 autobuild-preview-pr-268-ca208900 2026-08-24 12:36:24 UTC
3b900d41 autobuild-preview-pr-268-3b900d41 2026-08-23 08:45:27 UTC
33161987 autobuild-preview-pr-268-33161987 2026-08-21 08:19:58 UTC
e3c620df autobuild-preview-pr-268-e3c620df 2026-08-21 05:44:38 UTC
5f70edce autobuild-preview-pr-268-5f70edce 2026-07-18 02:47:42 UTC
86efe056 autobuild-preview-pr-268-86efe056 2026-07-16 01:46:56 UTC
0aef04ea autobuild-preview-pr-268-0aef04ea 2026-07-14 23:34:47 UTC
48232e38 autobuild-preview-pr-268-48232e38 2026-07-14 07:09:58 UTC
ee98a203 autobuild-preview-pr-268-ee98a203 2026-07-02 23:44:09 UTC

robobun added a commit to oven-sh/bun that referenced this pull request Jul 3, 2026
oven-sh/WebKit#268 settles the async function's promise before restoring
the async context, so a function that fails after an await reports its
rejection with its own context still installed. Un-skips the async-fn
fixture, which now passes on bun and node alike.

Pinned to the PR's preview build; re-pin to the autobuild tag of its
merge commit once it lands on WebKit main.
@robobun
robobun force-pushed the robobun/async-function-resume-context-ordering branch from ee98a20 to 48232e3 Compare July 14, 2026 06:26

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any issues, but this reorders exception-scope release and promise settlement in JSC's async function resume path, so it's worth a human sign-off.

What was reviewed:

  • Confirmed the new ordering matches PromiseReactionJob at JSMicrotask.cpp:1858-1867 (settle → restore).
  • putInternalField is a plain write-barrier store (InternalFieldTuple.h:75) — safe after scope.release() even with a pending exception from reject()/resolve().
  • Termination-exception early-return and the await-continuation arm still restore context correctly.
Extended reasoning...

Overview

Two-branch reorder in runInternalMicrotask's AsyncFunctionResume case (Source/JavaScriptCore/runtime/JSMicrotask.cpp). In both terminal arms — the error reject arm and the State::Executing resolve arm — the Bun async-context restore (asyncContextData->putInternalField(vm, 0, restoreAsyncContext)) is moved from before scope.release(); promise->reject/resolve(...) to after it. A comment is added referencing the identical ordering already used in PromiseReactionJob.

Security risks

None. The change is a reorder of two existing statements inside #if USE(BUN_JSC_ADDITIONS) blocks; no new inputs, no auth/crypto/permission surface.

Level of scrutiny

Moderate-to-high. The diff is tiny and mechanically mirrors the established PromiseReactionJob pattern in the same function (lines 1858-1867 and 1875-1880), which already does scope.release(); settle; restore;. I verified putInternalField is a bare WriteBarrier::set with no exception checks, so calling it after scope.release() with a possibly-pending exception is safe — same as the reference case. That said, this is the JSC microtask/async-resume hot path, where ordering between ThrowScope::release(), promise settlement, the rejection tracker callback, and async-context slot mutation is subtle and observable to embedders. Per the approval guidelines for critical runtime code paths, I'm deferring rather than auto-approving.

Other factors

  • The PR description includes a full audit table of every restoreAsyncContext site in the file; I spot-checked it and it's accurate.
  • Downstream tests exist in oven-sh/bun#31721 covering the unhandledRejection + AsyncLocalStorage scenario.
  • No prior human or bot review on the thread (CodeRabbit was rate-limited).
  • The bug hunting system found no issues.

robobun added a commit to oven-sh/bun that referenced this pull request Jul 14, 2026
oven-sh/WebKit#268 settles the async function's promise before restoring
the async context, so a function that fails after an await reports its
rejection with its own context still installed. Un-skips the async-fn
fixture, which now passes on bun and node alike.

Pinned to the PR's preview build (on top of WebKit 4895f45d, so it keeps
the shared-allocator change from #34009). Re-pin to the autobuild tag of
its merge commit once it lands on WebKit main.
robobun added a commit to oven-sh/bun that referenced this pull request Jul 14, 2026
…fixture cases

The claim that these arms were already correct was wrong: a .finally()
callback that returns a rejected thenable settles from
PromiseFinallyAwaitJob, which did not carry the async context across, so
the unhandledRejection handler observed undefined. Fixed alongside the
AsyncFunctionResume ordering in oven-sh/WebKit#268.

Add fixture cases for both .finally() shapes and two async-generator
shapes. The file is back in the tracking test's todos until the WebKit
pin picks up the PromiseFinallyAwaitJob fix.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any bugs, but this touches core JSC promise/async-context machinery and changes observable AsyncLocalStorage semantics, so it warrants a human look. Note that the diff also includes a second commit (PromiseFinallyAwaitJob context propagation) that isn't covered by the PR description — the audit table there marks PromiseFinallyReactionJob as "already correct", but this PR now modifies it.

What was reviewed:

  • AsyncFunctionResume reorder matches the PromiseReactionJob reference pattern exactly; putInternalField after scope.release() is a plain write-barrier store, safe post-release.
  • New InternalFieldTuple wrap/unwrap for PromiseFinallyAwaitJob follows the same shape as AsyncGeneratorYieldAwaited / PromiseReactionJob; the synchronous promiseFinallyAwaitJob(...) call sites keep passing raw context (no wrap needed since context is still installed).
  • Checked that restoreAsyncContext is only read when asyncContextData is non-null in all new arms.
Extended reasoning...

Overview

Single-file change to Source/JavaScriptCore/runtime/JSMicrotask.cpp with two logically separate pieces:

  1. AsyncFunctionResume settle ordering (matches title/description): reorders the error-reject and Executing-resolve arms to call promise->reject() / promise->resolve() before restoring the Bun async-context slot, so promiseRejectionTracker() and thenable-resolution jobs observe the async function's context. This is a two-line swap in each arm plus a comment, and mirrors the existing PromiseReactionJob ordering line-for-line.

  2. PromiseFinallyAwaitJob context propagation (second commit 0aef04ea, not in the PR description): promiseFinallyReactionJob now captures the current async context into an InternalFieldTuple alongside the reaction context when scheduling phase 2, and the PromiseFinallyAwaitJob case unwraps it and installs/restores around promiseFinallyAwaitJob(...). This is net-new behavior, though it follows the same InternalFieldTuple-wrap pattern already used by PromiseReactionJob, AsyncGeneratorYieldAwaited, and AsyncFromSyncIteratorContinue.

Security risks

None apparent. No parsing, no untrusted input handling, no auth/crypto. The change adjusts when a per-VM InternalFieldTuple slot is written and adds a heap allocation (InternalFieldTuple::create) on the .finally() scheduling path. The dynamicDowncast<InternalFieldTuple> discriminator is type-safe; the fallback uncheckedDowncast<JSSlimPromiseReaction> matches pre-existing behavior for the non-wrapped case.

Level of scrutiny

High. This is core JavaScriptCore promise/microtask machinery on the hot path of every await and .finally(). It intentionally changes what Bun's unhandledRejection handler and thenable-resolution jobs observe from AsyncLocalStorage, i.e., user-visible semantics. The change is small and pattern-following, but the surface it touches is exactly the kind where subtle ordering bugs (double-restore, leaked context, missed restore on early return) are easy to introduce and hard to detect. A maintainer familiar with the Bun async-context design should confirm the intended semantics — particularly for the second commit, which the PR description doesn't cover.

Other factors

  • The PR description's audit table is stale: it lists PromiseFinallyReactionJob as "already correct / unchanged", but the diff modifies it. The description should be updated or the second commit split out.
  • putInternalField is a simple WriteBarrier::set (checked InternalFieldTuple.h) — safe to call after scope.release(), consistent with all sibling arms.
  • The synchronous promiseFinallyAwaitJob(globalObject, vm, result, context, ...) call sites in promiseFinallyReactionJob correctly keep passing raw context rather than scheduledContext, since the async context is still installed at that point (phase-1 wrapper restores it).
  • Downstream test coverage exists in oven-sh/bun#31721 per the description, but no in-tree JSC tests are added here.
  • No prior human or bot reviews with substantive feedback on the timeline.

robobun added a commit to oven-sh/bun that referenced this pull request Jul 14, 2026
oven-sh/WebKit#268's second commit carries the async context through
PromiseFinallyAwaitJob, so a .finally() callback that returns a
rejected thenable now reports its unhandled rejection with the
callback's store. Un-skips the async-fn fixture (now 7 cases, all
passing on bun and node).

Also makes the "unhandledRejection async context" block concurrent —
8 hermetic subprocess spawns, so there's no reason to run them
sequentially.
@robobun
robobun force-pushed the robobun/async-function-resume-context-ordering branch from 0aef04e to 86efe05 Compare July 16, 2026 01:12
@robobun robobun changed the title JSC: keep async context installed across AsyncFunctionResume settle JSC: propagate async context through PromiseFinallyAwaitJob Jul 16, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any issues — the capture/install/restore logic is a close mirror of the existing PromiseFinallyReactionJob case and the other InternalFieldTuple-wrapped microtask handlers in this file. Deferring to a human because this sits on the JSC promise microtask dispatch path.

What was reviewed:

  • Confirmed the wrap/unwrap pattern matches phase 1 (PromiseFinallyReactionJob) and the ~8 other dynamicDowncast<InternalFieldTuple> sites in runInternalMicrotask.
  • Verified the two schedule points now carry scheduledContext while the synchronous promiseFinallyAwaitJob calls still receive the raw context (they run inside the phase-1 install window).
  • Checked that scope.release() ordering and the post-call putInternalField restore match the established pattern; non-Bun builds are unchanged behind #if USE(BUN_JSC_ADDITIONS).
Extended reasoning...

Overview

This PR modifies Source/JavaScriptCore/runtime/JSMicrotask.cpp to propagate Bun's async context (AsyncLocalStorage) through the PromiseFinallyAwaitJob internal microtask — the phase-2 job that runs when a .finally() callback returns a thenable. It has two pieces:

  1. In promiseFinallyReactionJob (phase 1), before scheduling phase 2 via performPromiseThenWithInternalMicrotask or createResolvingFunctionsWithInternalMicrotask, wrap the JSSlimPromiseReaction context in an InternalFieldTuple alongside the current async context (only when one is active).
  2. In the InternalMicrotask::PromiseFinallyAwaitJob case of runInternalMicrotask, unwrap that tuple, install the captured async context into globalObject->m_asyncContextData, run promiseFinallyAwaitJob, then restore the previous value.

Both pieces are guarded by #if USE(BUN_JSC_ADDITIONS); the non-Bun path is byte-for-byte unchanged.

Security risks

None identified. This is internal promise-reaction bookkeeping — no user-controlled input parsing, no auth/crypto/permissions surface. The uncheckedDowncast<JSSlimPromiseReaction> on the unwrapped field is safe because the only writer of that slot is the phase-1 code added in this same PR, which always stores a JSSlimPromiseReaction*. The InternalFieldTuple is GC-managed, so the captured context and async-context values are kept alive across the microtask boundary the same way they are in every other case in this file.

Level of scrutiny

High. runInternalMicrotask is on the hot path for every promise reaction in Bun, and mistakes in async-context install/restore can leak state across unrelated tasks. That said, the change is not novel logic — it is a near-verbatim copy of the PromiseFinallyReactionJob case immediately above it (JSMicrotask.cpp:2176-2214) and structurally identical to the async-generator and module cases at lines ~1940/2059/2088/2117/2146/2361. I compared each step (tuple detection, asyncContextData null-guard, restoreAsyncContext capture, scope.release() before the job call, restore after) and they line up exactly.

I also verified the four synchronous promiseFinallyAwaitJob(...) calls inside promiseFinallyReactionJob (non-object result, non-thenable, .then getter throw, non-callable .then) still pass the raw context, not scheduledContext — correct, since those run while phase 1's async context is still installed by the caller.

Other factors

  • The PR description includes an audit table of all internal-microtask cases that settle promises under an async context, with this being the last gap after #295.
  • Companion test coverage lives in oven-sh/bun#31721 (async-context-unhandled-rejection-async-fn.js), validated against Node.
  • No prior human review comments to address; only bot summaries in the timeline.

Given the critical code path, I'm deferring rather than auto-approving, but I have no concrete concerns to raise.

@robobun

robobun commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main: the AsyncFunctionResume commit was superseded by #295 (merged), so this PR now carries only the PromiseFinallyAwaitJob fix. Title and description updated to match.

robobun added a commit to oven-sh/bun that referenced this pull request Jul 18, 2026
oven-sh/WebKit#268 settles the async function's promise before restoring
the async context, so a function that fails after an await reports its
rejection with its own context still installed. Un-skips the async-fn
fixture, which now passes on bun and node alike.

Pinned to the PR's preview build (on top of WebKit 4895f45d, so it keeps
the shared-allocator change from #34009). Re-pin to the autobuild tag of
its merge commit once it lands on WebKit main.
robobun added a commit to oven-sh/bun that referenced this pull request Jul 18, 2026
…fixture cases

The claim that these arms were already correct was wrong: a .finally()
callback that returns a rejected thenable settles from
PromiseFinallyAwaitJob, which did not carry the async context across, so
the unhandledRejection handler observed undefined. Fixed alongside the
AsyncFunctionResume ordering in oven-sh/WebKit#268.

Add fixture cases for both .finally() shapes and two async-generator
shapes. The file is back in the tracking test's todos until the WebKit
pin picks up the PromiseFinallyAwaitJob fix.
robobun added a commit to oven-sh/bun that referenced this pull request Jul 18, 2026
oven-sh/WebKit#268's second commit carries the async context through
PromiseFinallyAwaitJob, so a .finally() callback that returns a
rejected thenable now reports its unhandled rejection with the
callback's store. Un-skips the async-fn fixture (now 7 cases, all
passing on bun and node).

Also makes the "unhandledRejection async context" block concurrent —
8 hermetic subprocess spawns, so there's no reason to run them
sequentially.
@robobun
robobun force-pushed the robobun/async-function-resume-context-ordering branch from 86efe05 to 5f70edc Compare July 18, 2026 02:09
@robobun

robobun commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main again: #301 introduced AsyncContextSwapScope, so this PR now uses it — one wrapWithCurrent call at the schedule point and one unwrapContextTuple + RAII scope in the case, matching PromiseFinallyReactionJob. 19 lines instead of 51.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Source/JavaScriptCore/runtime/JSMicrotask.cpp (1)

803-860: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Defer async-context wrapping until deferred work is needed.

wrapWithCurrent allocates an InternalFieldTuple whenever a context is active, but synchronous, primitive, non-thenable, and non-callable results execute inline and never use scheduledContext. Capture it immediately before the two deferred scheduling calls to avoid an allocation on the common synchronous path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Source/JavaScriptCore/runtime/JSMicrotask.cpp` around lines 803 - 860, Move
the AsyncContextSwapScope::wrapWithCurrent call out of the initial setup and
defer it until just before the deferred PromiseFinallyAwaitJob scheduling paths:
the fast JSPromise performPromiseThenWithInternalMicrotask call and
createResolvingFunctionsWithInternalMicrotask. Keep synchronous, primitive,
non-thenable, and non-callable branches using the original context directly,
while passing the wrapped context only to deferred work.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Source/JavaScriptCore/runtime/JSMicrotask.cpp`:
- Around line 803-860: Move the AsyncContextSwapScope::wrapWithCurrent call out
of the initial setup and defer it until just before the deferred
PromiseFinallyAwaitJob scheduling paths: the fast JSPromise
performPromiseThenWithInternalMicrotask call and
createResolvingFunctionsWithInternalMicrotask. Keep synchronous, primitive,
non-thenable, and non-callable branches using the original context directly,
while passing the wrapped context only to deferred work.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: afd1840e-84f7-4eb5-a62b-91526d29ce59

📥 Commits

Reviewing files that changed from the base of the PR and between 48232e3 and 5f70edc.

📒 Files selected for processing (1)
  • Source/JavaScriptCore/runtime/JSMicrotask.cpp

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any issues — the change mechanically applies the same wrapWithCurrent / unwrapContextTuple + AsyncContextSwapScope pattern already used by PromiseFinallyReactionJob and the other async-generator cases in this file. Deferring to a human because this is core JSC promise-reaction machinery and part of a subtle async-context series (#278/#295/#301).

What was reviewed:

  • wrapWithCurrent returns context unchanged when no async context is active, so the fast/non-ALS path and non-Bun builds (#else branch) are unaffected.
  • unwrapContextTuple mutates contextArg in place before the uncheckedDowncast<JSSlimPromiseReaction>, so the cast is still safe when a tuple was passed.
  • The synchronous promiseFinallyAwaitJob(...) calls still pass the raw context — correct, since they run inside the phase-1 scope's install window.
Extended reasoning...

Overview

The PR touches a single file, Source/JavaScriptCore/runtime/JSMicrotask.cpp, adding ~19 lines across two locations:

  1. In promiseFinallyReactionJob (phase 1), wrap the JSSlimPromiseReaction* context with the current Bun async context via AsyncContextSwapScope::wrapWithCurrent before handing it to the two schedule points that enqueue PromiseFinallyAwaitJob (performPromiseThenWithInternalMicrotask and createResolvingFunctionsWithInternalMicrotask).
  2. In the InternalMicrotask::PromiseFinallyAwaitJob dispatch case, unwrap that tuple with unwrapContextTuple and install the async context via an RAII AsyncContextSwapScope, exactly as the adjacent PromiseFinallyReactionJob case already does.

Both additions are guarded by #if USE(BUN_JSC_ADDITIONS); the #else branch keeps scheduledContext = context, so non-Bun builds see no behavioral or codegen change.

Security risks

None identified. This is internal promise-reaction plumbing with no user-controlled input beyond what already flows through the existing code paths. The added allocation (InternalFieldTuple::create inside wrapWithCurrent) is the same one already performed for the sibling reaction jobs and is GC-safe (all live values are on the stack or already stored in the reaction via setHandlerOrContext).

Level of scrutiny

This is core JSC promise-reaction runtime — every .finally() that returns a thenable flows through here — so it warrants a real look from someone who owns Bun's AsyncLocalStorage semantics. The change itself is mechanical (it copies the pattern from the case block ~20 lines above verbatim), but async-context propagation ordering has been subtle enough to need a multi-PR series (#278, #295, #301, and this one), so I'd rather a human confirm the settle-vs-restore ordering here matches the intended ALS semantics.

Other factors

  • The non-Bun fast path is preserved: wrapWithCurrent returns the raw context when no async context is active, so no extra allocation on the common path.
  • The four synchronous promiseFinallyAwaitJob(globalObject, vm, ..., context, ...) calls in promiseFinallyReactionJob intentionally keep the raw context rather than scheduledContext — they execute inside the phase-1 AsyncContextSwapScope, so wrapping would be redundant. The PR description calls this out.
  • unwrapContextTuple takes JSValue& and rewrites it to field 0 before returning field 1, so the subsequent uncheckedDowncast<JSSlimPromiseReaction>(contextArg) remains type-correct whether or not a tuple was passed.
  • Downstream test coverage exists in oven-sh/bun#31721.
  • Bug hunting system found nothing.

robobun added a commit to oven-sh/bun that referenced this pull request Jul 18, 2026
oven-sh/WebKit#268 rebased onto WebKit main a8d15c1c and rewritten to use
the AsyncContextSwapScope helper from #301, so it now matches every other
microtask case (one wrapWithCurrent at the schedule point, one
unwrapContextTuple + RAII scope in the case). Same behaviour, 19 lines
instead of 51.
robobun added a commit to oven-sh/bun that referenced this pull request Jul 18, 2026
oven-sh/WebKit#268 settles the async function's promise before restoring
the async context, so a function that fails after an await reports its
rejection with its own context still installed. Un-skips the async-fn
fixture, which now passes on bun and node alike.

Pinned to the PR's preview build (on top of WebKit 4895f45d, so it keeps
the shared-allocator change from #34009). Re-pin to the autobuild tag of
its merge commit once it lands on WebKit main.
robobun added a commit to oven-sh/bun that referenced this pull request Jul 18, 2026
…fixture cases

The claim that these arms were already correct was wrong: a .finally()
callback that returns a rejected thenable settles from
PromiseFinallyAwaitJob, which did not carry the async context across, so
the unhandledRejection handler observed undefined. Fixed alongside the
AsyncFunctionResume ordering in oven-sh/WebKit#268.

Add fixture cases for both .finally() shapes and two async-generator
shapes. The file is back in the tracking test's todos until the WebKit
pin picks up the PromiseFinallyAwaitJob fix.
robobun added a commit to oven-sh/bun that referenced this pull request Jul 18, 2026
oven-sh/WebKit#268's second commit carries the async context through
PromiseFinallyAwaitJob, so a .finally() callback that returns a
rejected thenable now reports its unhandled rejection with the
callback's store. Un-skips the async-fn fixture (now 7 cases, all
passing on bun and node).

Also makes the "unhandledRejection async context" block concurrent —
8 hermetic subprocess spawns, so there's no reason to run them
sequentially.
@robobun

robobun commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 1cb96a7 (#513), which oven-sh/bun main pins now. Both commits applied cleanly.

@robobun
robobun force-pushed the robobun/async-function-resume-context-ordering branch from 5791471 to 4b44c71 Compare August 25, 2026 21:57

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR and didn't find any bugs. Because it touches core JSC promise-reaction and microtask dispatch — a hot, correctness-sensitive path — a human look would still be worthwhile.

Checked that unwrapContextTuple handles every shape now passed as arguments[2] to PromiseResolveWithoutHandlerJob (undefined, null, and InternalFieldTuple from all four call sites) and leaves contextArg correctly typed for the uncheckedDowncast<JSSlimPromiseReaction> in PromiseFinallyAwaitJob.
Checked that moving the AsyncContextSwapScope above the species check in PromiseResolveThenableJobFast is safe — the RAII scope is torn down by RELEASE_AND_RETURN, and the slow path's performPromiseThen now captures the intended context.
Checked that the triggerPromiseReactions change stays inside the existing USE(BUN_JSC_ADDITIONS) block and reaches the 3-arg queueMicrotask, matching what the job case reads.

Extended reasoning...

Overview

The PR threads Bun's async context (AsyncLocalStorage) through two internal microtasks that previously dropped it: PromiseFinallyAwaitJob (phase 2 of .finally() when the callback returns a thenable) and PromiseResolveWithoutHandlerJob (handler-less side of a .then() reaction). It touches JSMicrotask.cpp (promiseFinallyReactionJob, the PromiseResolveThenableJobFast / PromiseResolveWithoutHandlerJob / PromiseFinallyAwaitJob cases in runInternalMicrotask) and JSPromise.cpp (performPromiseThen, performPromiseThenWithContext, triggerPromiseReactions). Every functional change is under #if USE(BUN_JSC_ADDITIONS).

Security risks

None identified. The change carries an existing internal JSValue (either jsUndefined() or an engine-created InternalFieldTuple) through an extra microtask argument slot and installs it via the existing AsyncContextSwapScope RAII helper. No new user-controlled input reaches a downcast or allocation path; unwrapContextTuple uses dynamicDowncast and tolerates empty/non-tuple values.

Level of scrutiny

High. performPromiseThen, triggerPromiseReactions, and runInternalMicrotask sit on the hot path of every promise chain in the engine. The change is small (~50 lines net) and closely mirrors the already-merged patterns for PromiseReactionJob and PromiseFinallyReactionJob, and the no-context fast paths are preserved (wrapWithCurrent returns the input unchanged when no async context is active). But a subtle mistake here — a leaked context, a wrong tuple field, or a mistyped uncheckedDowncast — would be a runtime-wide correctness or crash issue, and the reordering of the AsyncContextSwapScope relative to the species-watchpoint check in PromiseResolveThenableJobFast is the kind of thing a maintainer familiar with the microtask contract should sign off on.

Other factors

The PR description includes a thorough audit table and end-to-end verification against Node.js and the Next.js reproduction, and preview builds have been produced across multiple rebases. There are no outstanding human review comments and no prior review from me. The bug-hunting pass found nothing. Given the criticality of the code path, deferring rather than auto-approving.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 25, 2026
The unhandledRejection event is emitted from the end-of-tick drain, after
the context that was live at rejection time has been unwound, so
AsyncLocalStorage.getStore() observed undefined inside the listener.

promiseRejectionTracker(Reject) now queues the promise wrapped in an
AsyncContextFrame (via AsyncContextFrame::withAsyncContextIfNeeded) when a
context is active; both Handle-path lookups unwrap through one helper.
handleRejectedPromises installs the captured context around the dispatch
(replaying undefined for contextless entries so a re-entrant drain does not
leak the caller's store) and restores it afterwards. The microtask drains
in unhandled_rejection() and the auto-GC after the dispatch run under a
ClearedAsyncContextScope, as does the isBunTest early-return, since the
propagation machinery assumes the ambient slot is undefined during a
top-level drain.

A throwing unhandledRejection listener is reported after the slot is
cleared: EventEmitter gains an emit overload that returns the listener's
throw instead of reporting it inside the installed window. As in Node,
that throw also halts later listeners.

This pins the rejection-time context (Node >= 24 / async-context-frame
semantics), not the creation-time context Node 22's default ALS replays.

Tests: dual-runtime fixtures under async-context/ (sync rejections in
two stores, a contextless rejection, a timer rejection, a final in-context
rejection plus a context-free poll that catches a leaked slot; and the
async-fn shapes: await-throw, awaited native rejection, escaped async fn,
two async-generator shapes, both .finally() shapes), bun-only tests for
the rejection-time semantic, same-tick handling, the re-entrant drain,
strict mode (keeps the context for uncaughtException but not the drain),
the throwing listener with a persistent enterWith, and a spawned `bun
test` run; and the #32554 regression test parametrized with
AsyncLocalStorage so its in-flight scan sees frame-wrapped entries.

The async-fn fixture stays in the tracking test's todos until
WEBKIT_VERSION picks up oven-sh/WebKit#268, which carries the remaining
JSC half (PromiseFinallyAwaitJob did not carry the async context across).
robobun added a commit to oven-sh/bun that referenced this pull request Aug 25, 2026
…tures

- Every new comment in src/ is one line; the reasoning lives in the PR and
  commit messages.
- The exception-returning emit overload delegates to fireEventListeners,
  which takes the optional out-parameter, instead of duplicating its body.
- The dual-runtime test.each tables skip the node leg when node is absent.
- Both fixtures fail on an unknown or duplicate delivery. The async-fn
  fixture's await-throw case resumes from a macrotask (the cross-tick
  shape) and the rest resume from a microtask; the one case that needs
  oven-sh/WebKit#268 moves to its own fixture so the other six are
  enforced now, and only that fixture is in the tracking test's todos.
robobun added a commit to oven-sh/bun that referenced this pull request Aug 25, 2026
oven-sh/WebKit#268, rebased onto current WebKit main, carries the async
context through PromiseFinallyAwaitJob, so a .finally() callback that
returns a rejected thenable now reports its rejection with the callback's
store. The finally-thenable fixture passes on bun and node and comes out
of the tracking test's todos.

The preview's asset set matches main's current autobuild exactly (42
tarballs). Re-pin to the merge commit's autobuild before merging.
robobun added a commit to oven-sh/bun that referenced this pull request Aug 25, 2026
…dlerJob fix

The previous preview (e3c620df) sat one WebKit commit behind main's pin,
so it lacked the FTL FFI fix from oven-sh/WebKit#477 and ffi.test.js
crashed on both Windows lanes. The new preview (33161987) is
oven-sh/WebKit#268 on top of current WebKit main.

oven-sh/WebKit#268 gained a second commit. A promise that adopts another
promise's settlement without a handler of its own (p.then(f) with p
rejecting, resolve(otherPromise), an async function returning a promise)
is settled by PromiseResolveWithoutHandlerJob, and JSC dropped the
context then() had captured on every path that queues that job. Those
derived promises are the rejections Next.js 16 filters by reading its
prerender store from the unhandledRejection listener (#39847); with the
bun-side change alone that app still logged two rejections per request,
with this preview it logs none, the same as node.

async-context-unhandled-rejection-then-passthrough.js covers the four
shapes on bun and node. All four observe a null store on the previous
preview.
robobun added a commit to oven-sh/bun that referenced this pull request Aug 25, 2026
Main moved WEBKIT_VERSION to aea1f010 (oven-sh/WebKit#330, the buffer
accessor intrinsic that #35343 registers). oven-sh/WebKit#268 is rebased
onto that commit, so its preview carries everything main's pin has plus
the two async context fixes.
@robobun

robobun commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 7688227, which oven-sh/bun main pins now. Both commits applied cleanly.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

@robobun

robobun commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 2da33d5 (#519), which oven-sh/bun main pins now. Both commits applied cleanly.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

@robobun

robobun commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 7259739 (#521), which oven-sh/bun main pins now. Both commits applied cleanly.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

@robobun

robobun commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Preview builds for this branch cannot complete at the moment: the GitHub-hosted windows-11-arm job fails within seconds at "Install LLVM and Ninja (ARM64)" on nearly every run today (main's own autobuild 33065639924 for 568ccc2 fails the same way, at 12:26 and again at 15:53). The other 42 jobs pass. I rerun it until it lands on a working runner; the code is unchanged.

@robobun

robobun commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main d9feedf to pick up #523 (the Windows arm64 job no longer depends on scoop), which is what kept the previous previews from completing. Both commits applied cleanly.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 0bb01ed, which oven-sh/bun main pins now. Both commits applied cleanly.

1 similar comment
@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 0bb01ed, which oven-sh/bun main pins now. Both commits applied cleanly.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto f5deafe, which oven-sh/bun main pins now. Both commits applied cleanly.

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

When a .finally() callback returns a thenable, the follow-up that
propagates the original fulfilment (or rejects with the thenable's
reason) runs as InternalMicrotask::PromiseFinallyAwaitJob. Unlike
PromiseFinallyReactionJob, this case did not install the async context,
and the phase-1 handler did not capture it on the reaction it schedules,
so an unhandled rejection originating here observed an undefined
AsyncLocalStorage store.

Capture the active async context alongside the reaction at the two
schedule points in promiseFinallyReactionJob (wrapWithCurrent), and
install/restore it in the PromiseFinallyAwaitJob case (unwrapContextTuple
+ AsyncContextSwapScope), mirroring PromiseFinallyReactionJob. The
synchronous promiseFinallyAwaitJob calls remain inside the phase-1
install window and are unaffected.
A reaction registered with then() captures the async context, but when
the settled side has no handler the derived promise is settled by
PromiseResolveWithoutHandlerJob, and every path that queues that job
dropped the context: triggerPromiseReactions for a JSFullPromiseReaction
with an undefined side, the already-settled arms of performPromiseThen
and performPromiseThenWithContext, and the PromiseResolveThenableJobFast
reaction that makes one promise adopt another. The derived promise then
rejected with no async context installed, so the rejection tracker (and
Bun's unhandledRejection event) saw none, while the same shape with a
handler, or through the non-fast thenable job, kept it.

Pass the reaction's context as the job's third argument and install it
around the settle, the way PromiseReactionJob does. The thenable fast
path installs the captured context before the species check too, so the
slow path's performPromiseThen captures the same context.
@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 1817c3c (the #528 upstream merge), which oven-sh/bun main pins now. Both commits applied cleanly and compile against that base.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

oven-sh/bun#31721 no longer pins a preview of this PR; it builds on bun main's pin and keeps the two fixtures that need this change as todos. So this PR is no longer on the bun PR's critical path, but the finally-thenable and then-passthrough shapes (and the Next.js case in oven-sh/bun#39847) stay broken until it lands. The head is rebased onto 1817c3c and compiles there.

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