Skip to content

fix: drop stale autosaves after newer foreground writes#9239

Merged
mscolnick merged 2 commits intomarimo-team:mainfrom
shaun0927:fix/autosave-rename-overwrite
Apr 17, 2026
Merged

fix: drop stale autosaves after newer foreground writes#9239
mscolnick merged 2 commits intomarimo-team:mainfrom
shaun0927:fix/autosave-rename-overwrite

Conversation

@shaun0927
Copy link
Copy Markdown
Contributor

This pull request was authored by a coding agent.

📝 Summary

Closes #9227.

This prevents a queued code-mode autosave from overwriting newer notebook content after a rename or explicit foreground save.

🔍 Description of Changes

Queued autosaves currently capture only a cells snapshot. If a rename or foreground save happens before that queued work runs, the autosave can still write into the newer file state and revert the notebook to older content.

Root Cause

  • _maybe_autosave() currently queues only cells_snapshot.
  • The queued work does not capture filename or any foreground-write generation.
  • save_from_cells() writes to whatever self._filename is when the queued work eventually runs.
  • Foreground writes and autosaves share a lock, but there is no invalidation step that says “a newer foreground write has superseded this older queued autosave.”

Changes

  • Add an autosave target token in AppFileManager made of:
    • the expected filename
    • a foreground-write generation counter
  • Capture that token when queueing autosave work.
  • Drop queued autosaves if a rename or foreground write has already superseded the captured state.
  • Add a regression test that reproduces the stale-autosave overwrite ordering bug.

This keeps the fix narrow: foreground writes still run synchronously, and FIFO autosave ordering relative to other autosaves is preserved.

Tests

uv run --group test pytest tests/_session/extensions/test_notification_listener_autosave.py -q
uv run --group test pytest tests/_server/test_file_manager.py -k 'save_from_cells_persists_cells or save_from_cells_preserves_layout_file or save_and_save_from_cells_serialize_under_lock' -q
uv run ruff check marimo/_session/extensions/extensions.py marimo/_session/notebook/file_manager.py tests/_session/extensions/test_notification_listener_autosave.py

📋 Checklist

  • I have read the contributor guidelines.
  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Tests have been added for the changes made.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Pull request title is a good summary of the changes - it will be used in the release notes.

Queued code-mode autosaves currently capture only cells, so a snapshot created before a rename or explicit save can later write into the newer file state. This change tags queued autosaves with the filename and foreground-write generation they were created against, then drops them if a foreground write has already superseded that state.

Constraint: Keep the fix narrow and preserve FIFO autosave ordering relative to other autosaves
Rejected: Route all foreground saves through SerialTaskRunner | broader runtime/save semantic change than needed for the validated bug
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Any future autosave work should preserve the invariant that foreground writes supersede older queued snapshots
Tested: uv run --group test pytest tests/_session/extensions/test_notification_listener_autosave.py -q
Tested: uv run --group test pytest tests/_server/test_file_manager.py -k 'save_from_cells_persists_cells or save_from_cells_preserves_layout_file or save_and_save_from_cells_serialize_under_lock' -q
Tested: uv run ruff check marimo/_session/extensions/extensions.py marimo/_session/notebook/file_manager.py tests/_session/extensions/test_notification_listener_autosave.py
Not-tested: Browser-level marimo-pair/code-mode end-to-end flow
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Apr 17, 2026 0:04am

Request Review

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 17, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@shaun0927
Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@shaun0927
Copy link
Copy Markdown
Contributor Author

recheck

@mscolnick mscolnick marked this pull request as ready for review April 17, 2026 13:20
Copilot AI review requested due to automatic review settings April 17, 2026 13:20
@mscolnick mscolnick added the bug Something isn't working label Apr 17, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a stale queued autosave path where an older code-mode autosave snapshot could overwrite newer notebook content after a rename or explicit foreground save.

Changes:

  • Add an autosave “target token” (filename + foreground-write generation) to detect stale queued autosaves.
  • Capture and pass the token when queueing autosave work; drop autosaves that no longer match current filename/generation.
  • Add a regression test covering rename + foreground save ordering vs queued autosave.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
marimo/_session/notebook/file_manager.py Introduces autosave generation tracking, capture API, and stale-autosave dropping checks inside save_from_cells.
marimo/_session/extensions/extensions.py Captures autosave target before enqueueing autosave work and passes expected filename/generation to save_from_cells.
tests/_session/extensions/test_notification_listener_autosave.py Updates autosave snapshot tests for new save_from_cells signature and adds a regression test for stale autosave ordering.
Comments suppressed due to low confidence (1)

marimo/_session/notebook/file_manager.py:478

  • save_from_cells can now intentionally return an empty string when the autosave is dropped due to expected_generation/expected_filename mismatch, but the docstring still implies it only raises on unnamed notebooks or write failures and otherwise persists. Please update the docstring/return contract to reflect the new “dropped autosave” behavior (and what callers should expect).
        """Persist the notebook from a snapshot of document cells.

        Used by the server-side auto-save path for ``code_mode``
        mutations. Unlike ``save()``, this takes cells directly — the
        caller is responsible for snapshotting ``session.document.cells``
        on a thread where the document is quiescent.

        Raises:
            HTTPException: If the notebook is unnamed or the write fails
        """

Comment on lines 306 to 312
with self._save_lock:
if self._is_same_path(new_path):
return new_path.name

self._assert_path_does_not_exist(new_path)
self._invalidate_autosaves()

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

_invalidate_autosaves() is called before performing the rename/write. If storage.rename() or _save_file() raises, the foreground operation didn’t actually supersede anything, but already-queued autosaves will now be treated as stale and dropped. Consider invalidating only after the rename + _save_file() succeed (still under _save_lock) so failed renames don’t disable pending autosaves.

Copilot uses AI. Check for mistakes.
Comment on lines 387 to 391
with self._save_lock:
self.app.update_config(config)
if self._filename is not None:
self._invalidate_autosaves()
return self._save_file(
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

Autosaves are invalidated before _save_file() runs. If _save_file() fails, pending autosaves will be dropped even though no successful foreground write occurred. Consider incrementing the autosave generation only after _save_file() succeeds (while still holding _save_lock).

Copilot uses AI. Check for mistakes.
Comment on lines +450 to 456
if request.persist:
self._invalidate_autosaves()
return self._save_file(
filename_path,
notebook=self.app.to_ir(),
persist=request.persist,
)
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

Autosaves are invalidated before _save_file() executes. If the foreground save fails (e.g., storage write error), queued autosaves will be considered stale and skipped even though the newer content never reached disk. Consider invalidating only after _save_file() succeeds (still under _save_lock).

Suggested change
if request.persist:
self._invalidate_autosaves()
return self._save_file(
filename_path,
notebook=self.app.to_ir(),
persist=request.persist,
)
result = self._save_file(
filename_path,
notebook=self.app.to_ir(),
persist=request.persist,
)
if request.persist:
self._invalidate_autosaves()
return result

Copilot uses AI. Check for mistakes.
@mscolnick mscolnick merged commit 4091e04 into marimo-team:main Apr 17, 2026
44 of 46 checks passed
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.

Queued code-mode autosave can overwrite newer content after rename + foreground save

3 participants