DATA LOSS: muse shelf save silently drops unstaged modifications when any other file is staged
Second data point on the same underlying defect family — found while finalizing the Episode 15 demo script, reproduced cleanly:
muse init
echo "..." > pipeline.py && muse code add . && muse commit -m "Initial" ...
<edit pipeline.py>
muse code add pipeline.py # STAGE it (nothing else touched)
muse shelf save --by claude-code --intent-type interrupt -m "test" --resumable
Shelf save reports success and correctly captures pipeline.py's new
content in the shelf snapshot this time (files_count: 1, matches). But
muse status --json immediately after shows clean: false, with
pipeline.py listed as modified in BOTH staged.modified AND
unstaged.modified simultaneously — the working tree was never reverted
to HEAD at all. The change sits there uncommitted, still staged, exactly
as if shelf save had never run its working-tree-reset step.
Contrast with the two clean cases already confirmed:
- Change is unstaged only (never staged) -> shelf save reverts correctly,
clean: trueafterward. Works. - Change is staged only, and it's the ONLY change in the repo -> shelf save captures it correctly but does NOT revert the working tree/index. Broken, this comment.
- Mixed staged + unstaged (different files) -> the unstaged file is dropped from the snapshot entirely. Data loss, main report above.
Working theory, not yet verified: shelf save's post-snapshot cleanup
step only clears the working tree for paths it read from an UNSTAGED
diff pass, and never resets the STAGED index/tree for paths that came
from the staged side — which would explain both symptoms as one bug:
whatever code path builds the snapshot from staged content never also
schedules those same paths for the reset-to-HEAD step. Worth checking
first when someone picks this up.
HIGHEST SEVERITY — real, permanent, silent data loss of uncommitted work. Found while building Episode 15 ("Shelves") demo material. Confirmed 3 times, in 3 independently constructed minimal repros. Assigning to myself to circle back asap, per the same discipline as staging#90.
Repro (minimal, deterministic):
What happened:
file_a.txt's real, unstaged edit ("line A -- EDITED, UNSTAGED") is not present in the shelf's stored snapshot manifest (confirmed by reading the shelf's raw object off disk under.muse/shelf/sha256/...and decoding the blob-wrapped JSON — thesnapshotdict has nofile_a.txtkey at all, staged, unstaged, or otherwise). It is not in the object store as any reachable reference. The working tree then revertsfile_a.txtback to the last-committed content, exactly as if the checkpoint had succeeded. There is no warning, no nonzero exit code, nowarningsfield entry —status: "shelved"is reported as an unconditional success.Isolated to the interaction, not either operation alone:
apply/pop. Confirmed working.Also observed:
muse status --jsonimmediately after the lossy shelve reports a confusing/inconsistent state —newfile.txt(the file that WAS staged and correctly shelved) shows up in bothstaged.addedANDunstaged.deleted, and previously-tracked.museattributes/.museignoreshow asuntracked. This smells like the shelf-save snapshot-building code is operating over some intermediate/partial index representation rather than the true reconciled working-tree state, and whatever it consumes from gets left in a semi-mutated state afterward.Why this is the most severe finding of the season: every other bug found in Build With Muse involved wrong metadata, an unreachable feature, or a wrong risk score — recoverable, or at worst misleading. This one silently destroys real uncommitted work during what the user believes is a safe checkpoint operation, which is the exact scenario
muse shelfexists to protect against ("Full snapshots, not deltas. The complete working-tree manifest is stored, so restoration is always unambiguous" — directly contradicted).Next step: read
muse/cli/commands/shelf.py's snapshot-construction code (likely wherever it enumerates changed paths for the save operation) and determine whether it's reading from a stale/partial in-memory index vs. re-scanning the true working tree + stage state. Priority: fix before recommendingmuse shelffor any real work, agent or human.Assigned to gabriel. Circling back on this immediately after wrapping the current episode script, not deferred to "after the season" like the other tickets.