fix(thenable): use Object.create() to avoid Promise mutation in headless Chromium#10653
fix(thenable): use Object.create() to avoid Promise mutation in headless Chromium#10653Zelys-DFKH wants to merge 2 commits intoTanStack:mainfrom
Conversation
…ess Chromium Resolves TanStack#10509 where useQuery stays pending indefinitely in Puppeteer/Playwright. Headless Chromium enforces stricter Promise semantics, treating internal slots as sealed. The custom properties set on the Promise (status, resolve, reject) were silently failing in these environments. By wrapping the Promise with Object.create() instead of mutating it directly, we preserve all Promise behavior via the prototype chain while allowing custom properties to live on the wrapper object. This maintains full backward compatibility - all consumers call .then() which continues to work via prototype chain inheritance, and the notification callbacks now fire correctly in all JavaScript environments.
|
ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR refactors pendingThenable to derive the exported thenable from an internal Promise via Object.create(), suppresses unhandled rejection noise with a no-op .catch(), and adds a changeset entry documenting the fix for headless Chromium Promise mutation. ChangesThenable Promise Mutation Fix
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run-many --target=build --exclude=examples/*... |
❌ Failed | 4s | View ↗ |
☁️ Nx Cloud last updated this comment at 2026-05-07 23:10:22 UTC
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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.
Inline comments:
In `@packages/query-core/src/thenable.ts`:
- Around line 57-59: Creating thenable via Object.create(promise) yields an
object missing Promise internal slots so React's Suspense/use will throw when
calling then; fix by copying/binding the Promise prototype methods from the
underlying promise onto the PendingThenable instance (e.g., set thenable.then =
promise.then.bind(promise), thenable.catch = promise.catch.bind(promise),
thenable.finally = promise.finally.bind(promise)) so that thenable (returned as
nextResult.promise) behaves like a real Promise while still carrying the extra
status/result fields.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 380bf36f-7464-4be4-ab48-a6ff0f738d58
📒 Files selected for processing (2)
.changeset/fix-promise-mutation-10509.mdpackages/query-core/src/thenable.ts
…n React Suspense The Object.create(promise) wrapper pattern requires explicit method binding. Without it, React's Suspense and other code calling .then() fails with 'Method Promise.prototype.then called on incompatible receiver' because the native Promise implementation requires internal slots [[PromiseState]] and [[PromiseResult]] to exist on the object .then() is called on. Binding (e.g., thenable.then = promise.then.bind(promise)) ensures the methods execute in the correct context (the underlying promise with slots) while preserving all custom wrapper properties (status, resolve, reject).

Your issue nailed it. Headless Chromium treats Promise internal slots as sealed, so mutations like
thenable.status = 'pending'silently fail. The.then()handler fires, but the wrapper never updates — notification callbacks never run, and useQuery stays pending forever.It's a small but real problem for testing. I've spent hours debugging similar patterns.
The Fix
Create a wrapper object with the Promise as its prototype instead of mutating the Promise directly. This lets custom properties live on the wrapper while preserving all Promise semantics:
The wrapper's custom properties work. The
.then()chain works via prototype. No behavior changes, fully backward compatible.Files Changed
packages/query-core/src/thenable.ts— Wrap the Promise instead of mutating itTesting
Verify in headless mode:
Fixes #10509
Summary by CodeRabbit