gabriel / muse public
fix patch fix/207-shelf-save-staged-unstaged-data-loss #1 / 1
AI Agent gabriel · 10 hours ago · Oct 22, 2026 · Diff

fix: muse shelf save silently dropped unstaged modifications when any other file was staged -- DATA LOSS (musehub#207)

Root cause: run_save() (and its programmatic twin _shelf_push_programmatic) computed the working-tree manifest via plugin.snapshot(root)[files]. For a StagePlugin (the code domain), snapshot() is documented and used by muse commit to mean 'what would be committed right now' -- when any file is staged, it overlays staged object IDs onto HEAD content, making every OTHER tracked file's genuine unstaged edit invisible by design (correct for commit's selective-commit model, since unstaged changes must never leak into a commit). Shelf save reused that same commit-semantics manifest as if it were the complete working-tree manifest its own docstring promises -- it is not, whenever a stage is active.

Confirmed live, exactly matching the reported repro: with file_b.txt staged and file_a.txt separately edited but never staged, the computed manifest's file_a.txt entry equaled its HEAD value byte-for-byte despite the real on-disk content differing. The subsequent working-tree revert to HEAD then silently overwrote file_a.txt's real edit with no error, no warning, and status: shelved reported as success.

A second, compounding symptom traced to the same interaction: the stage index was never cleared after a successful unscoped shelve, so a staged-only file's content correctly reverted to HEAD on disk but its stale stage entry remained -- the same path then appeared in both staged.modified and unstaged.modified simultaneously.

Fix: - Added _true_workdir_manifest(): uses plugin.workdir_snapshot() -- already the existing, tested method muse diff --working uses to bypass the stage overlay -- for StagePlugin-conformant plugins, falling back to snapshot() unchanged for domains with no staging concept at all. Applied at both manifest-computation call sites (run_save and _shelf_push_programmatic). This also fixes the same latent bug in muse shelf save --path, which shared the same buggy manifest source. - Call plugin.clear_stage(root) after a successful unscoped apply_manifest() revert, at both call sites -- the shelf now fully captures every staged AND unstaged change, so any remaining stage entry afterward would be stale.

sha256:3c17bfbda4675d79c8fd3f331c927292363bd41abfbaa7b8d3c4da7c161b00a5 sha
+20 ~2 symbols
2 changed · 1261 in snapshot files
sha256:a3103e3f8f1dac89cd0aca2722c6f108e27f75f7e4271abdf2ffbffb0326944f snapshot
+20
symbols added
~2
symbols modified
2
files changed
1261
files in snapshot
0
dead code introduced
Semantic Changes 22 symbols
+ TestCleanCasesStillWork class class TestCleanCasesStillWork L134–164
+ test_staged_only_addition_shelves_correctly method method test_staged_only_addition_shelves_correctly L152–164
+ test_unstaged_only_change_shelves_and_restores_correctly method method test_unstaged_only_change_shelves_and_restores_correctly L137–150
+ TestDataLossOnMixedStagedUnstaged class class TestDataLossOnMixedStagedUnstaged L61–105
+ test_popping_the_shelf_restores_both_edits method method test_popping_the_shelf_restores_both_edits L91–105
+ test_unstaged_edit_to_other_file_is_not_lost method method test_unstaged_edit_to_other_file_is_not_lost L64–89
+ TestStageNotClearedAfterUnscopedSave class class TestStageNotClearedAfterUnscopedSave L108–131
+ test_status_is_clean_after_shelving_a_staged_only_change method method test_status_is_clean_after_shelving_a_staged_only_change L112–131
+ _invoke function function _invoke L46–52
+ CliRunner import import CliRunner L40–40
+ InvokeResult import import InvokeResult L40–40
+ annotations import import annotations L32–32
+ json import import json L34–34
+ os import import os L35–35
+ pathlib import import pathlib L36–36
+ pytest import import pytest L38–38
+ repo function function repo L56–58
+ runner variable variable runner L43–43
~ muse/cli/commands/shelf.py .py 2 symbols added, 2 symbols modified
+ _true_workdir_manifest function function _true_workdir_manifest L405–429
+ StagePlugin import import StagePlugin L116–116
Files Changed
+1 ~1
1261 in snapshot
← Older Oldest on fix/207-shelf-save-staged-unstaged-data-loss
All commits
Newer → Latest on fix/207-shelf-save-staged-unstaged-data-loss

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:3c17bfbda4675d79c8fd3f331c927292363bd41abfbaa7b8d3c4da7c161b00a5 --body "your comment"