WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Conversation

@samihamine
Copy link

@samihamine samihamine commented Dec 4, 2025

Fix cell duplication when YStore is out-of-sync with file

Fixes jupyterlab/jupyterlab#14031


Summary

This PR fixes a cell duplication bug that occurs when the SQLite YStore content differs from the file on disk (out-of-sync condition). The bug manifests after laptop sleep/wake cycles or prolonged disconnections.


Root Cause Analysis

Why is this bug INTERMITTENT (random)?

The bug only occurs when ALL of these conditions are met:

  1. User was connected to a notebook
  2. Disconnection lasted longer than document_cleanup_delay (default: 60s)
  3. Server cleaned up the room and YStore has accumulated history
  4. Upon reconnection, YStore content differs from file content

This explains why:

  • Short disconnections → No duplication (room still exists)
  • First connection ever → No duplication (no YStore history)
  • Long sleep/wake cycles → Duplication possible (if YStore ≠ file)

Evidence from Production Logs

We analyzed 15 reconnection events across two production incidents.

Statistical Correlation

Scenario Count out-of-sync? Duplication?
Direct load from file (no YStore) 2 No No
YStore loaded + in-sync with file 0 No No
YStore loaded + out-of-sync 13 Yes Yes

Correlation: 100% - Every out-of-sync event led to duplication.


Log Extract: Case WITHOUT duplication (first connection)

[I 2025-11-28 19:49:46.477] Initializing room json:notebook:04b6c8ed-fb1a-43f3-80ea-b3ad7f805f84
[D 2025-11-28 19:49:46.478] Sending SYNC_STEP1 message to endpoint
[I 2025-11-28 19:49:46.541] Content in room loaded from file notebooks/.../notebook.ipynb  ← DIRECT FROM FILE
[D 2025-11-28 19:49:46.552] Received SYNC_STEP1 message from endpoint
[D 2025-11-28 19:49:46.552] Sending SYNC_STEP2 message to endpoint
[I 2025-11-28 19:49:47.596] Saving file: notebooks/.../notebook.ipynb
                           ← NO DuplicateCellId!

Log Extract: Case WITHOUT duplication (first connection, no YStore)

[I 2025-11-28 19:49:46.477 ServerApp] Initializing room json:notebook:04b6c8ed-fb1a-43f3-80ea-b3ad7f805f84
[D 2025-11-28 19:49:46.478 ServerApp] Sending SYNC_STEP1 message to endpoint: json:notebook:04b6c8ed-fb1a-43f3-80ea-b3ad7f805f84
[I 2025-11-28 19:49:46.541 ServerApp] Content in room json:notebook:04b6c8ed-fb1a-43f3-80ea-b3ad7f805f84 loaded from file notebooks/98f257b8-8f9d-4936-be4a-f5f342b66e11/1764359382662-98f257b8.ipynb
[D 2025-11-28 19:49:46.552 ServerApp] Received SYNC_STEP1 message from endpoint: json:notebook:04b6c8ed-fb1a-43f3-80ea-b3ad7f805f84
[D 2025-11-28 19:49:46.552 ServerApp] Sending SYNC_STEP2 message to endpoint: json:notebook:04b6c8ed-fb1a-43f3-80ea-b3ad7f805f84
[D 2025-11-28 19:49:46.553 ServerApp] Received AWARENESS message from endpoint: json:notebook:04b6c8ed-fb1a-43f3-80ea-b3ad7f805f84
[D 2025-11-28 19:49:46.553 ServerApp] Received SYNC_STEP2 message from endpoint: json:notebook:04b6c8ed-fb1a-43f3-80ea-b3ad7f805f84
[D 2025-11-28 19:49:46.593 ServerApp] Received SYNC_UPDATE message from endpoint: json:notebook:04b6c8ed-fb1a-43f3-80ea-b3ad7f805f84
[D 2025-11-28 19:49:46.594 ServerApp] Writing Y update to YStore
[I 2025-11-28 19:49:47.594 ServerApp] Saving the content from room json:notebook:04b6c8ed-fb1a-43f3-80ea-b3ad7f805f84
[I 2025-11-28 19:49:47.596 YDocExtension] Saving file: notebooks/98f257b8-8f9d-4936-be4a-f5f342b66e11/1764359382662-98f257b8.ipynb
[D 2025-11-28 19:49:47.596 ServerApp] Saving /srv/work/notebooks/98f257b8-8f9d-4936-be4a-f5f342b66e11/1764359382662-98f257b8.ipynb

Content loaded from file (direct load, no YStore) → NO DuplicateCellId


Log Extract: Case WITH duplication (reconnection after sleep, YStore out-of-sync)

[I 2025-12-02 20:16:41.780 ServerApp] Initializing room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.780 ServerApp] Sending SYNC_STEP1 message to endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[W 2025-12-02 20:16:41.783 ServerApp] Notebook notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is not trusted
[I 2025-12-02 20:16:41.786 ServerApp] Content in room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 loaded from the ystore SQLiteYStore
[I 2025-12-02 20:16:41.786 ServerApp] Content in file notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is out-of-sync with the ystore SQLiteYStore
[I 2025-12-02 20:16:41.786 ServerApp] Content in room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 loaded from file notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-02 20:16:41.819 ServerApp] Received SYNC_STEP1 message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.819 ServerApp] Sending SYNC_STEP2 message to endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.819 ServerApp] Received AWARENESS message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.983 ServerApp] Received SYNC_STEP2 message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.984 ServerApp] Sending Y update to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.984 ServerApp] Writing Y update to YStore
[I 2025-12-02 20:16:42.985 ServerApp] Saving the content from room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[I 2025-12-02 20:16:42.987 YDocExtension] Saving file: notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-02 20:16:42.988 ServerApp] Saving /srv/work/notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[W 2025-12-02 20:16:42.988 ServerApp] Notebook notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is not trusted
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'e3ee9c07-2e30-43a7-b17b-daa26572dfc4' detected. Corrected to 'bc5d4e70'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '52b3b11c-366d-4119-acc8-00539627127b' detected. Corrected to '4549ddd6'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'a5c1970c-6cd5-4d50-9b17-cefc5caa722a' detected. Corrected to 'acb8ae4a'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '58a07b40-c4cc-4c3b-a3b3-bf8b084cd75d' detected. Corrected to '4e2611cd'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '7c539002-168a-4d76-85f3-e3e923e14e4c' detected. Corrected to '08d6f588'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '3ba22ec0-da72-41de-a347-a43d9aff2078' detected. Corrected to '72d50b21'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '72eacc00-d1bd-430a-b5ac-c8ef3a699a67' detected. Corrected to 'e60cd8bf'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'c3d097da-f0f2-4011-8b08-8a24773ae46a' detected. Corrected to '16d163db'.
  validate(nb)
  1. Content loaded from the ystore SQLiteYStore → YStore was loaded first
  2. Content in file ... is out-of-sync with the ystoreOUT-OF-SYNC DETECTED (TRIGGER)
  3. Content loaded from file → File content applied via set() which does MERGE
  4. 8 DuplicateCellId errors → Duplication occurred

Technical Deep Dive

The Feedback Loop (Why duplications accumulate)

┌─────────────────────────────────────────────────────────────────────────────────┐
│                           FEEDBACK LOOP                                          │
│                                                                                  │
│  1. User reconnects after sleep > document_cleanup_delay                        │
│     │                                                                            │
│     ▼                                                                            │
│  2. Server creates new room, loads YStore history into YDoc                     │
│     │                                                                            │
│     ▼                                                                            │
│  3. YStore content ≠ File content → OUT-OF-SYNC detected                        │
│     │                                                                            │
│     ▼                                                                            │
│  4. set() called with file content on a YDoc that already has YStore content    │
│     │                                                                            │
│     ▼                                                                            │
│  5. set() tries to MERGE (retain cells by ID) instead of REPLACE                │
│     │                                                                            │
│     ▼                                                                            │
│  6. Cells with same ID but different Y.js clientID → DUPLICATION                │
│     │                                                                            │
│     ▼                                                                            │
│  7. nbformat.validate() detects duplicates, RENAMES IDs to fix                  │
│     │                                                                            │
│     ▼                                                                            │
│  8. File saved with NEW cell IDs                                                │
│     │                                                                            │
│     ▼                                                                            │
│  9. YStore still has OLD cell IDs → Next reconnection = OUT-OF-SYNC again       │
│     │                                                                            │
│     └──────────────────────────── BACK TO STEP 3 ───────────────────────────────┘
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

This explains why duplications accumulate over multiple reconnections:

  • Bug 1 logs: 5 duplicated cells → 11 → 11 → 11 → ... (stabilizes after all cells are duplicated)

Why set() causes MERGE instead of REPLACE

Looking at jupyter_ydoc/ynotebook.py, the set() method (lines 233-321):

def set(self, value: dict) -> None:
    new_cells = value["cells"]
    old_ycells_by_id = {ycell["id"]: ycell for ycell in self._ycells}
    
    with self._ydoc.transaction():
        retained_cells = set()
        
        for new_cell in new_cells:
            cell_id = new_cell.get("id")
            if cell_id and (old_ycell := old_ycells_by_id.get(cell_id)):
                # Cell with same ID exists → try to UPDATE granularly
                if self._update_cell(old_cell, new_cell, old_ycell):
                    retained_cells.add(cell_id)  # ← RETAINED, not replaced!
                    continue
            # ... add as new cell

The problem: When YStore has cells [A, B, C] and file has cells [A, B, C] with the same IDs, set() tries to retain them. But the Y.js internal state (clientID, clock) differs, causing the merge to create duplicates.


The Fix

Fix 1: Clear YDoc on out-of-sync (rooms.py)

When out-of-sync is detected, we now clear the YDoc cells before calling set():

if self._document.source != model["content"]:
    # FIX: Clear the document content BEFORE loading from file to prevent
    # cell duplication. When out-of-sync is detected, the YDoc contains
    # content from the YStore that may have corrupted state.
    self._clear_document()
    read_from_source = True

The _clear_document() method:

def _clear_document(self) -> None:
    """
    Clears the document content to allow a clean reload from file.
    """
    if hasattr(self._document, "ycells"):
        with self._document.ydoc.transaction():
            self._document.ycells.clear()
        self.log.info("Cleared document content in room %s to prevent duplication", self._room_id)

This ensures set() receives an empty YDoc, so all cells come fresh from the file with no merge conflicts.

Fix 2: Reorder initialize/serve (handlers.py) - (DEFENSE IN DEPTH)

The original code had a race condition:

# BEFORE (problematic):
async def open(self, room_id):
    self.create_task(self._websocket_server.serve(self))  # ← Starts serving immediately
    await self.room.initialize()  # ← Init happens in parallel

This allowed SYNC_STEP1 to be sent before initialization completed. We now ensure initialization completes first:

# AFTER (fixed):
async def open(self, room_id):
    await self.room.initialize()  # ← Complete initialization first
    self.create_task(self._websocket_server.serve(self))  # ← Then start serving

Why Both Fixes Are Needed

Fix Problem Solved Sufficient Alone?
rooms.py (clear on out-of-sync) Prevents merge duplication Yes - addresses root cause
handlers.py (reorder init/serve) Prevents race condition No - doesn't fix corrupted YStore

The rooms.py fix is critical as it directly prevents the merge that causes duplication. The handlers.py fix is defense in depth - good practice but won't help if YStore is already out-of-sync.


Testing

  • Analyzed production logs: 100% correlation between out-of-sync and duplication
  • Verified fix targets the exact code path shown in logs
  • ycells.clear() is the same method used internally by set() when no cells are retained

This commit addresses the cell duplication bug reported in jupyterlab/jupyterlab#14031.

Root cause analysis from production logs:
- When initialize() detects an out-of-sync between YStore and file
- The YDoc already contains content from YStore (potentially corrupted)
- Calling set() tries to MERGE instead of REPLACE
- This causes cell duplication

Fixes:
1. handlers.py: Reorder initialize() BEFORE serve() to prevent race condition
2. rooms.py: Clear YDoc cells when out-of-sync is detected before loading from file

Tested with production logs showing 100% correlation between out-of-sync and duplication.
@github-actions
Copy link
Contributor

github-actions bot commented Dec 4, 2025

Binder 👈 Launch a Binder on branch samihamine/jupyter-collaboration/fix%2Fcell-duplication-out-of-sync

Copy link
Member

@krassowski krassowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR!

Looking at jupyter_ydoc/ynotebook.py, the set() method (lines 233-321):

The code which you highlight was only introduced 2 weeks ago, but issue that you are addressing was opened over two years ago.

If that code is really the culprit, can you confirm:

  • it started two weeks ago for you
  • downgrading to jupyter-ydoc v3.1.0 resolves the issue for you

But the Y.js internal state (clientID, clock) differs, causing the merge to create duplicates.

This is kind of irrelevant to set() implementation because it operates on serialized value.

Comment on lines +153 to +160
# FIX: Clear the document content BEFORE loading from file to prevent
# cell duplication. When out-of-sync is detected, the YDoc contains
# content from the YStore that may have corrupted state (e.g., from
# previous duplications). If we don't clear it, the set() method will
# try to MERGE the YStore content with the file content instead of
# REPLACING it, which causes cell duplication.
# See: https://github.com/jupyterlab/jupyterlab/issues/14031
self._clear_document()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing it here would undo the benefits of work done to address #509 and jupyterlab/jupyterlab#18100, e.g.

set() method will try to MERGE the YStore content with the file content instead of REPLACING it

This should be:

  • tested (there should be a test showing the problem, failing now, passing after a test)
  • addressed in jupyter-ydoc set() method for notebook, not in this package
  • hard reload should be only performed IF required to prevent UI side-effects

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be easier to test this by adding a UI test here, even if the fix ultimately goes to the jupyter-ydoc package because this repo has UI tests setup.

@krassowski krassowski added the bug Something isn't working label Dec 4, 2025
@samihamine
Copy link
Author

@krassowski

We've experienced this bug on both versions of jupyter-ydoc:

  • Before 3.2.0 (old set() with clear() + extend())
  • with 3.2.1 (new set() with retained_cells logic)

So the retained_cells code introduced 2 weeks ago i think is not the root cause.

Looking more carefully at the log sequence:

[D 2025-12-02 20:16:41.777 ServerApp] Accepting token-authenticated request from 128.79.69.44
[I 2025-12-02 20:16:41.777 YDocExtension] Creating FileLoader for: notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[I 2025-12-02 20:16:41.779 YDocExtension] Watching file: notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-02 20:16:41.779 ServerApp] 101 GET /api/collaboration/room/json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2?sessionId=5a8863b7-4093-4453-ad7f-b122b1c98160 ([email protected]) 2.73ms
[I 2025-12-02 20:16:41.780 ServerApp] Initializing room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.780 ServerApp] Sending SYNC_STEP1 message to endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[W 2025-12-02 20:16:41.783 ServerApp] Notebook notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is not trusted
[I 2025-12-02 20:16:41.786 ServerApp] Content in room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 loaded from the ystore SQLiteYStore
[I 2025-12-02 20:16:41.786 ServerApp] Content in file notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is out-of-sync with the ystore SQLiteYStore
[I 2025-12-02 20:16:41.786 ServerApp] Content in room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 loaded from file notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-02 20:16:41.819 ServerApp] Received SYNC_STEP1 message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.819 ServerApp] Sending SYNC_STEP2 message to endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.819 ServerApp] Received AWARENESS message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.819 ServerApp] Sending Y awareness from client with endpoint json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.983 ServerApp] Received SYNC_STEP2 message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.984 ServerApp] Sending Y update to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:41.984 ServerApp] Writing Y update to YStore
[I 2025-12-02 20:16:42.985 ServerApp] Saving the content from room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[I 2025-12-02 20:16:42.987 YDocExtension] Saving file: notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-02 20:16:42.988 ServerApp] Saving /srv/work/notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[W 2025-12-02 20:16:42.988 ServerApp] Notebook notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is not trusted
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'e3ee9c07-2e30-43a7-b17b-daa26572dfc4' detected. Corrected to 'bc5d4e70'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '52b3b11c-366d-4119-acc8-00539627127b' detected. Corrected to '4549ddd6'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'a5c1970c-6cd5-4d50-9b17-cefc5caa722a' detected. Corrected to 'acb8ae4a'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '58a07b40-c4cc-4c3b-a3b3-bf8b084cd75d' detected. Corrected to '4e2611cd'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '7c539002-168a-4d76-85f3-e3e923e14e4c' detected. Corrected to '08d6f588'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '3ba22ec0-da72-41de-a347-a43d9aff2078' detected. Corrected to '72d50b21'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '72eacc00-d1bd-430a-b5ac-c8ef3a699a67' detected. Corrected to 'e60cd8bf'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'c3d097da-f0f2-4011-8b08-8a24773ae46a' detected. Corrected to '16d163db'.
  validate(nb)
[D 2025-12-02 20:16:43.000 ServerApp] Sending Y update to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 20:16:43.000 ServerApp] Writing Y update to YStore
...
...
...
[I 2025-12-02 22:33:25.713 YDocExtension] Creating FileLoader for: notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[I 2025-12-02 22:33:25.714 YDocExtension] Watching file: notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-02 22:33:25.714 ServerApp] 101 GET /api/collaboration/room/json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2?sessionId=5a8863b7-4093-4453-ad7f-b122b1c98160 ([email protected]) 1.99ms
[I 2025-12-02 22:33:25.714 ServerApp] Initializing room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 22:33:25.715 ServerApp] Sending SYNC_STEP1 message to endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[W 2025-12-02 22:33:25.719 ServerApp] Notebook notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is not trusted
[I 2025-12-02 22:33:25.723 ServerApp] Content in room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 loaded from the ystore SQLiteYStore
[I 2025-12-02 22:33:25.725 ServerApp] Content in file notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is out-of-sync with the ystore SQLiteYStore
[I 2025-12-02 22:33:25.725 ServerApp] Content in room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 loaded from file notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-02 22:33:25.759 ServerApp] Received SYNC_STEP1 message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 22:33:25.759 ServerApp] Sending SYNC_STEP2 message to endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 22:33:25.760 ServerApp] Received AWARENESS message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 22:33:25.760 ServerApp] Sending Y awareness from client with endpoint json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 22:33:26.056 ServerApp] Received SYNC_STEP2 message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 22:33:26.057 ServerApp] Sending Y update to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 22:33:26.058 ServerApp] Writing Y update to YStore
[I 2025-12-02 22:33:27.058 ServerApp] Saving the content from room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[I 2025-12-02 22:33:27.062 YDocExtension] Saving file: notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-02 22:33:27.062 ServerApp] Saving /srv/work/notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[W 2025-12-02 22:33:27.062 ServerApp] Notebook notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is not trusted
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'e3ee9c07-2e30-43a7-b17b-daa26572dfc4' detected. Corrected to 'becd7bef'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '52b3b11c-366d-4119-acc8-00539627127b' detected. Corrected to 'f92f22a9'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'a5c1970c-6cd5-4d50-9b17-cefc5caa722a' detected. Corrected to 'f8281e5a'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '58a07b40-c4cc-4c3b-a3b3-bf8b084cd75d' detected. Corrected to 'd51c060f'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '7c539002-168a-4d76-85f3-e3e923e14e4c' detected. Corrected to 'eff54f79'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '3ba22ec0-da72-41de-a347-a43d9aff2078' detected. Corrected to '59d4ee23'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '72eacc00-d1bd-430a-b5ac-c8ef3a699a67' detected. Corrected to '94757071'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'c3d097da-f0f2-4011-8b08-8a24773ae46a' detected. Corrected to 'fa1cecda'.
  validate(nb)
[D 2025-12-02 22:33:27.083 ServerApp] Sending Y update to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 22:33:27.083 ServerApp] Writing Y update to YStore
[D 2025-12-02 22:33:27.083 ServerApp] Sending Y update to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-02 22:33:27.083 ServerApp] Writing Y update to YStore
...
...
...
[I 2025-12-03 08:23:21.342 YDocExtension] Creating FileLoader for: notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[I 2025-12-03 08:23:21.343 YDocExtension] Watching file: notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-03 08:23:21.343 ServerApp] 101 GET /api/collaboration/room/json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2?sessionId=5a8863b7-4093-4453-ad7f-b122b1c98160 ([email protected]) 1.73ms
[I 2025-12-03 08:23:21.343 ServerApp] Initializing room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-03 08:23:21.344 ServerApp] Sending SYNC_STEP1 message to endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[W 2025-12-03 08:23:21.349 ServerApp] Notebook notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is not trusted
[I 2025-12-03 08:23:21.354 ServerApp] Content in room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 loaded from the ystore SQLiteYStore
[I 2025-12-03 08:23:21.356 ServerApp] Content in file notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is out-of-sync with the ystore SQLiteYStore
[I 2025-12-03 08:23:21.357 ServerApp] Content in room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 loaded from file notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-03 08:23:21.389 ServerApp] Received SYNC_STEP1 message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-03 08:23:21.389 ServerApp] Sending SYNC_STEP2 message to endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-03 08:23:21.390 ServerApp] Received AWARENESS message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-03 08:23:21.390 ServerApp] Sending Y awareness from client with endpoint json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2 to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-03 08:23:22.350 ServerApp] Received SYNC_STEP2 message from endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-03 08:23:22.351 ServerApp] Sending Y update to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-03 08:23:22.351 ServerApp] Writing Y update to YStore
[I 2025-12-03 08:23:23.351 ServerApp] Saving the content from room json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[I 2025-12-03 08:23:23.356 YDocExtension] Saving file: notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[D 2025-12-03 08:23:23.356 ServerApp] Saving /srv/work/notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb
[W 2025-12-03 08:23:23.356 ServerApp] Notebook notebooks/59756c74-0855-441f-96c5-8486ae9c966a/1764700977411-59756c74.ipynb is not trusted
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'e3ee9c07-2e30-43a7-b17b-daa26572dfc4' detected. Corrected to '2309edbe'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '52b3b11c-366d-4119-acc8-00539627127b' detected. Corrected to '36081b11'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'a5c1970c-6cd5-4d50-9b17-cefc5caa722a' detected. Corrected to '684a9bd0'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '58a07b40-c4cc-4c3b-a3b3-bf8b084cd75d' detected. Corrected to '52a362d8'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '7c539002-168a-4d76-85f3-e3e923e14e4c' detected. Corrected to '072582ec'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '3ba22ec0-da72-41de-a347-a43d9aff2078' detected. Corrected to '02a49cb0'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id '72eacc00-d1bd-430a-b5ac-c8ef3a699a67' detected. Corrected to 'fb83138d'.
  validate(nb)
/opt/conda/lib/python3.13/site-packages/nbformat/__init__.py:132: DuplicateCellId: Non-unique cell id 'c3d097da-f0f2-4011-8b08-8a24773ae46a' detected. Corrected to '53d6c7f3'.
  validate(nb)
[D 2025-12-03 08:23:23.381 ServerApp] Sending Y update to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-03 08:23:23.381 ServerApp] Writing Y update to YStore
[D 2025-12-03 08:23:23.381 ServerApp] Sending Y update to client with endpoint: json:notebook:8a962d66-7756-4da7-b888-6fbdfdc64ce2
[D 2025-12-03 08:23:23.382 ServerApp] Writing Y update to YStore
...
...
Step Example 1 Example 2 Example 3
loaded from file 20:16:41.786 22:33:25.725 08:23:21.357
Received SYNC_STEP2 20:16:41.983 (+197ms) 22:33:26.056 (+331ms) 08:23:22.350 (+993ms)
DuplicateCellId 20:16:42.988 22:33:27.062 08:23:23.356

In all 3 cases, the duplication is detected AFTER Received SYNC_STEP2, not immediately after loaded from file. This indicates the duplication occurs during Yjs sync merge, not during the set() call.

The actual mechanism:

  1. Server initializes YDoc from file
  2. Client (after sleep/wake) still has its old YDoc with cells
  3. Client sends SYNC_STEP2 with its cells
  4. Yjs merges both → cells with same IDs but different Yjs item identifiers → duplication

About the _clear_document(), fix, you're right, clearing here doesn't address the root cause and would break the UX improvements from #355 and #360. I'll remove this.

About handlers.py change, the reordering of initialize() before serve() is still valid as defense-in-depth, but won't solve the duplication if the client has stale state.

Some questions i have in mind if you can help with please :

  1. Should the fix be client-side (reset YDoc on reconnection after prolonged disconnection)?
  2. Is there a server-side mechanism to force client YDoc reset when out-of-sync is detected?

@davidbrochart
Copy link
Collaborator

1. Should the fix be client-side (reset YDoc on reconnection after prolonged disconnection)?

That's something we've been thinking about, and it would also help with managing the size of the YStore (see here).

2. Is there a server-side mechanism to force client YDoc reset when out-of-sync is detected?

What do you mean by out-of-sync? Technically client and server are always out-of-sync.

I think we could introduce some kind of session ID into the shared document, and when the room is cleared and the shared document recreated in the backend, change this ID. When the client sees that its session ID doesn't match, it recreates a fresh shared document which syncs with the backend. But this would require cooperation from the frontend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Damaged notebook (duplicate cells) in RTC-active hosted hub

3 participants