Paired notebooks + jupyter-collaboration: saving one file silently discards unsaved edits in its twin
Summary
- A paired notebook is two files,
demo.ipynbanddemo.py. With jupyter-collaboration, each open file also has a live document in memory. - Someone edits
demo.pyin one tab whiledemo.ipynbis open in another. - Saving the notebook makes Jupytext rewrite
demo.pyon disk, as designed. - jupyter-collaboration sees the file change, trusts the disk, and replaces the open
demo.pydocument. Everything typed there since its last save is lost, with no dialog. - It happens at every save, autosave included, in both directions. The next save from the overwritten editor is refused as
OutOfBandChanges, which is the "File Changed" dialog of #1447.
Neither package is wrong on its own; each assumes it is the only writer.
Impact
On a JupyterHub used for a hackathon (2026-09-14), participants lost work repeatedly. We
counted 431 Out-of-band changes. Overwriting the content in room events that day; for
three of the four participants behind almost all of them, 97–100 % followed a save of
another document in the same server within 3 s (73 % for the fourth; chance level about
16 %), almost always notebook saved, then .py overwritten. We removed pairing from all course material,
but cannot rule it out server-side (proposal 2).
Reproduction
pip install jupyterlab jupytext jupyter-collaboration- Pair
demo.ipynbwithformats: ipynb,py:percent. - Open
demo.ipynbas a notebook anddemo.pyin the text editor. - Type in
demo.pywithout saving, then edit and savedemo.ipynb. - The unsaved text in
demo.pyis replaced; savingdemo.pyraises "File Changed".
Root cause
Jupytext writes the twin through the contents manager (async_pairs.write_pair →
save_one_file → self.super.save), with force_update_timestamp=True, so the twin's
mtime always moves. jupyter-server-ydoc only trusts timestamps it recorded itself:
FileLoader.maybe_notify sees a newer last_modified, every subscribed room reloads
from disk (rooms.py), and maybe_save_content then raises OutOfBandChanges. A write
made by the server itself is indistinguishable from an external edit.
Proposals
- Do not write a twin behind an open room. When the twin has a live room, push the
new content into its shared document (keeps unsaved edits), or at least update the
room's recorded
last_modified(removes the false signal). Either would close #1447. - A switch to ignore pairing declared in notebook metadata, e.g.
JupytextConfiguration.allow_notebook_formats_metadata = False(defaultTrue). Todaynotebook_formats()always honours a notebook's ownjupytext.formats, so a hub operator cannot guarantee pairing is off. - Warn when a paired save happens with
jupyter_server_ydocenabled, and document the limitation. We had no signal until work was lost.
Versions
jupytext 1.19.5 · jupyterlab 4.6.3 · jupyter-collaboration 5.0.2 · jupyter-server-ydoc 3.0.2 · jupyter-ydoc 4.1.1 · pycrdt 0.14.4 · Python 3.12
We can test candidate fixes on a live multi-user hub and contribute a reproduction test next to the collaboration load tests of #1552. Possibly related: #1501.
Source: jupytext/jupytext