firebase / firebase/firebase-js-sdk
hardAssert throws on transient state, permanently poisons Firestore SDK instance (React 19 StrictMode)
- Dominant language
- TypeScript
- Stars
- 5.1k
- Forks
- 1k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 37
Description
### Operating System
macOS
### Environment (if applicable)
React 19, app wrapped in ``. In development, React Strict Mode intentionally re-runs mount/effect logic, which increases the likelihood of overlapping `getDoc()` calls for the same document. React documents these Strict Mode checks as development-only.
### Firebase SDK Version
`firebase 11.10.0` (`@firebase/firestore 4.8.0`) — also reproduces on `11.6.0`
### Firebase SDK Product(s)
Firestore
### Project Tooling
- `firebase 11.10.0` (`@firebase/firestore 4.8.0`) — also reproduces on `11.6.0`
- React 19 with ``
- `initializeFirestore(app, { localCache: memoryLocalCache() })`
### Detailed Problem Description
An internal assertion failure in `@firebase/firestore` appears to leave the Firestore client permanently unusable for the rest of the page session. Once the assertion fires, subsequent Firestore operations fail with cascading internal assertion / AsyncQueue errors until the page is reloaded.
### Expected behavior
A transient internal state inconsistency should not leave the Firestore client permanently unusable for the rest of the page session. If this path is truly unrecoverable, the failure mode should ideally be more contained, or the SDK should expose a supported way to recover / recreate the client cleanly.
### Actual behavior
After the first internal assertion fires, the Firestore client becomes unusable for the remainder of the session. Subsequent operations fail until full page reload.
### Steps and code to reproduce issue
#### Steps
1. Wrap the app in ``.
2. On auth state change, call `getDoc(doc(db, 'users', uid))` inside a `useEffect`.
3. In development, Strict Mode re-runs the effect, making overlapping reads of the same document easier to trigger.
4. In this repro, the overlapping reads appear to create duplicate internal target lifecycle activity.
5. The failure seems to occur in `TargetState.recordTargetResponse()` when `ve` is decremented below zero.
6. `__PRIVATE_hardAssert(this.ve >= 0, 3241, { ve: this.ve })` throws, after which subsequent Firestore operations fail for the remainder of the session.
#### Assertion location
```js
// TargetState.recordTargetResponse()
We() {
this.ve -= 1;
__PRIVATE_hardAssert(this.ve >= 0, 3241, { ve: this.ve });
}
Contributor guide
Assessment
This issue has not been assessed yet.