The 0.2.20 release notes document a known limitation: an async describe body suspends with its prefix active, so a sibling describe registering during the suspension inherits it ("A > B > y" for a test declared in B alone).
The fix, designed and verified
Make the describe prefix an AsyncContext.Variable (like currentTest already is), and route every runtime registration call through a Snapshot captured at module init — because registering a bun:test test from inside any AsyncLocalStorage frame makes bun hang waiting for a phantom done callback (bun 1.3.14; probe-verified, run(undefined, ...) frames included). Context is read before the hop, carried in wrapper closures; the runtime never executes inside our frames. The trampoline also makes tests-registering-tests from inside tracked bodies safe.
An 82-line implementation exists and the escape mechanism is probe-proven with AsyncLocalStorage.snapshot().
Why it's blocked
Using raw AsyncLocalStorage alongside @b9g/async-context means two context abstractions in one module — rejected. The shim's AsyncSnapshot cannot express the escape today: it restores by nesting run() frames, which is (a) a spec violation for post-capture variables and (b) still a frame, so bun still hangs. Both are fixed by backing AsyncSnapshot with native AsyncLocalStorage.snapshot() — filed as bikeshaving/shovel#120.
When shovel#120 lands: swap the trampoline to the shim's AsyncSnapshot (captured at module init), apply the parked implementation, and delete the known-limitation note. Regression test to add: async describe suspending mid-registration while a sibling registers, asserting snapshot keys A > x and B > y (never A > B > y), on both runtimes.
Also worth filing upstream (independent)
The bun behavior itself — registration inside an ALS frame breaking the runner's completion tracking — is arguably a bun bug regardless of what shovel does.
The 0.2.20 release notes document a known limitation: an
asyncdescribe body suspends with its prefix active, so a sibling describe registering during the suspension inherits it ("A > B > y"for a test declared in B alone).The fix, designed and verified
Make the describe prefix an
AsyncContext.Variable(likecurrentTestalready is), and route every runtime registration call through a Snapshot captured at module init — because registering abun:testtest from inside any AsyncLocalStorage frame makes bun hang waiting for a phantomdonecallback (bun 1.3.14; probe-verified,run(undefined, ...)frames included). Context is read before the hop, carried in wrapper closures; the runtime never executes inside our frames. The trampoline also makes tests-registering-tests from inside tracked bodies safe.An 82-line implementation exists and the escape mechanism is probe-proven with
AsyncLocalStorage.snapshot().Why it's blocked
Using raw
AsyncLocalStoragealongside@b9g/async-contextmeans two context abstractions in one module — rejected. The shim'sAsyncSnapshotcannot express the escape today: it restores by nestingrun()frames, which is (a) a spec violation for post-capture variables and (b) still a frame, so bun still hangs. Both are fixed by backingAsyncSnapshotwith nativeAsyncLocalStorage.snapshot()— filed as bikeshaving/shovel#120.When shovel#120 lands: swap the trampoline to the shim's
AsyncSnapshot(captured at module init), apply the parked implementation, and delete the known-limitation note. Regression test to add: async describe suspending mid-registration while a sibling registers, asserting snapshot keysA > xandB > y(neverA > B > y), on both runtimes.Also worth filing upstream (independent)
The bun behavior itself — registration inside an ALS frame breaking the runner's completion tracking — is arguably a bun bug regardless of what shovel does.