Skip to content

@tanstack/electric-db-collection: The pending sync transaction is already committed, you can't commit it again #1122

@sudoskys

Description

@sudoskys
  • I've validated the bug against the latest version of DB packages

Describe the bug

When using electricCollectionOptions with syncMode: 'progressive', the application throws SyncTransactionAlreadyCommittedError or SyncTransactionAlreadyCommittedWriteError after a browser visibility change (tab switch, window minimize/restore, etc.).

The error occurs because the visibilityHandler triggers resume_fn, which attempts to write to or commit a sync transaction that has already been committed. The transaction state (transactionStarted) is not properly reset when resuming from a visibility change.

Package Versions

  • @tanstack/db: ^0.5.18
  • @tanstack/electric-db-collection: ^0.2.22
  • @tanstack/react-db: ^0.1.62
  • @electric-sql/client: (latest)

To Reproduce

  1. Create an Electric collection with syncMode: 'progressive':

    const eventsCollection = createCollection(
      electricCollectionOptions({
        id: `events-${chatId}`,
        schema: mySchema,
        shapeOptions: { ... },
        getKey: item => item.id,
        syncMode: 'progressive',
      }),
    );
  2. Perform a mutation that triggers data sync (e.g., insert new records)

  3. While the sync is in progress, switch to another browser tab or minimize the window

  4. Return to the tab/window

  5. Observe the error in the console

Error Stack Trace

Uncaught TransactionError: The pending sync transaction is already committed, you can't commit it again.
    TanStackDBError errors.ts:4
    TransactionError errors.ts:272
    SyncTransactionAlreadyCommittedError errors.ts:338
    commit sync.ts:177
    unsubscribeStream electric.ts:1504
    publish_fn client.ts:1398
    publish_fn client.ts:1396
    promise callback*publish_fn client.ts:1394
    onMessages_fn client.ts:1088
    requestShapeLongPoll_fn client.ts:1155
    fetchShape_fn client.ts:1133
    requestShape_fn client.ts:749
    start_fn client.ts:664
    resume_fn client.ts:1285
    visibilityHandler client.ts:1427

Also occurs as a write error:

Uncaught TransactionError: The pending sync transaction is already committed, you can't still write to it.
    TanStackDBError errors.ts:4
    TransactionError errors.ts:272
    SyncTransactionAlreadyCommittedWriteError errors.ts:324
    write sync.ts:112
    processChangeMessage electric.ts:1278
    unsubscribeStream electric.ts:1377
    publish_fn client.ts:1398
    ...
    visibilityHandler client.ts:1427

Expected behavior

The sync transaction state should be properly reset when visibilityHandler triggers resume_fn. The application should handle visibility changes gracefully without throwing transaction errors.

Root Cause Analysis

Looking at the Electric collection source code, in progressive mode:

const isBufferingInitialSync = () =>
  syncMode === `progressive` && !hasReceivedUpToDate

// ...

if (isBufferingInitialSync() && !transactionStarted) {
  bufferedMessages.push(message)
} else {
  if (!transactionStarted) {
    begin()
    transactionStarted = true
  }
  processChangeMessage(message)
}

The issue appears to be:

  1. User performs a mutation, sync begins
  2. Messages are being processed, transaction is started and committed
  3. User switches tabs (visibility hidden)
  4. User returns to tab, visibilityHandler triggers resume_fn
  5. resume_fn calls start_fn which re-subscribes to the shape
  6. New messages arrive and attempt to write/commit to the already-committed transaction
  7. transactionStarted flag is out of sync with actual transaction state

Suggested Fix: Reset transactionStarted to false when the visibility handler resumes, or ensure the transaction state is properly synchronized before processing new messages after a resume.

Workaround

Removing syncMode: 'progressive' resolves the issue:

const eventsCollection = createCollection(
  electricCollectionOptions({
    id: `events-${chatId}`,
    schema: mySchema,
    shapeOptions: { ... },
    getKey: item => item.id,
    // syncMode: 'progressive', // Disabled due to visibility resume bug
  }),
);

Desktop

  • OS: Linux (Arch Linux, kernel 6.17.9)
  • Browser: Firefox (latest)
  • Also reproducible on Chrome

Additional context

This bug is particularly problematic for applications where users frequently switch between tabs while real-time sync operations are in progress. The error is not recoverable without a page refresh, which disrupts the user experience significantly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions