Repository navigation
Description
Duplicate issue
- Related issues searched. Unlike [BUG] Promise-returning killHandler is acknowledged before cleanup finishes #190, this is a browser prototype-inspection regression introduced in fix!: align task lifecycle and runtime contracts #196, not premature cleanup acknowledgement.
Description
Browser promise detection calls Object.getPrototypeOf() before assimilating ordinary thenables. A valid thenable proxy can reject solely because its unrelated prototype trap throws. Normal tasks, abortable tasks and cleanup handlers are affected.
Steps to reproduce
Use the browser bundle built from #196 at 69ec6511deb18761ae6eb6fb4eb841c47e3bfc3c. Serve these files alongside dist/browser/mod.js.
worker.js:
import { ThreadWorker } from './dist/browser/mod.js'
new ThreadWorker(() => new Proxy(
{ then(resolve) { resolve(42) } },
{ getPrototypeOf() { throw new Error('opaque prototype') } },
))Browser entry module:
import { FixedThreadPool } from './dist/browser/mod.js'
const pool = new FixedThreadPool(1, new URL('./worker.js', import.meta.url))
if (!pool.info.ready) {
await new Promise(resolve => {
pool.eventTarget.addEventListener('ready', resolve, { once: true })
})
}
try {
console.log(await pool.execute())
} finally {
await pool.destroy()
}Expected behavior
Resolve 42 by invoking the callable then. Thenable assimilation does not require prototype inspection.
Actual behavior
Execution rejects with opaque prototype before invoking then. Passing a fresh abort signal produces the same failure. An equivalent proxy resolving undefined from killHandler also causes a cleanup failure event.
Environment and controls
Confirmed with real workers in Chromium 155. Pre-port 05f4fb22 resolves 42 with and without a signal and accepts the cleanup proxy. Upstream poolifier a9c3ce08 also passes all three cases on Node v26.11.1: native promise branding skips prototype inspection for this proxy. No published package version was tested.
Cause and suggested fix
Browser fallback at src/worker/abstract-worker.ts:889-897 admits every object when isPromise is unavailable. Isolate optional native-promise probing so an unsuccessful probe falls back to captured-then assimilation, without suppressing actual then getter errors. Preserve single-read and original-receiver guarantees. This is not the documented modified-Promise-constructor limitation; the proxy wraps an ordinary thenable.
Activity
fix(worker): preserve opaque browser thenables