Skip to content

hardAssert throws on transient state, permanently poisons Firestore SDK instance (React 19 StrictMode) #9729

@ShaneBreazeale

Description

@ShaneBreazeale

Operating System

macOS

Environment (if applicable)

React 19, app wrapped in <StrictMode>. 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 <StrictMode>
  • 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 <React.StrictMode>.
  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

// TargetState.recordTargetResponse()
We() {
    this.ve -= 1;
    __PRIVATE_hardAssert(this.ve >= 0, 3241, { ve: this.ve });
}

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions