fix: muse code add silently drops a file replacing a tracked empty directory of the same path
Discovered live in a real user repo: 'muse code add .' reported a file as staged ('new file'), muse status even showed it correctly staged briefly, but after 'muse commit' the file reverted to untracked -- as if nothing had happened. Reproduced deterministically:
mkdir target-path && muse code add target-path && muse commit ... rmdir target-path && echo content > target-path muse code add . # reports 'target-path: new file' AND 'target-path/: deleted dir' muse commit ... # target-path is in NEITHER the manifest NOR directories afterward
Root cause: muse/cli/commands/code_stage.py::run_add stages new/modified files into updated_stage[rel] first (the 'collected' loop), then -- completely unconditionally -- a second pass for committed empty directories no longer present on disk writes updated_stage[rel_dir] = make_entry(EMPTY_DIR_OID, 'D') for every path in deleted_committed_dirs. Since a file and a directory-sentinel share one flat stage keyspace (bare path string -> one entry), and this repo's path had just transitioned from tracked-empty-directory to real file, both passes targeted the identical dict key -- and the second, unconditional write silently clobbered the first. The file's stage entry never existed by the time write_stage() ran; the object was written to the store, but committed nowhere.
Fix, two parts: 1. code_stage.py: the deleted-committed-dirs loop now skips any path already present in updated_stage from this same invocation -- a directory-to-file transition needs exactly one stage entry (the file), not a clobbering second write to the same key. 2. plugins/code/plugin.py: defense-in-depth invariant in snapshot()'s stage branch -- staged_dirs now explicitly excludes every path present in staged_manifest, so a path can never be recorded as both a file and a directory in the same commit even if some other staging code path fails to explicitly record the transition. This is the same corruption shape as musehub#101 (files double-recorded as directories in historical commits) -- this fix closes the live commit-time source of that corruption, not just a historical symptom of it.
Added TestDirToFileTransitionStageClobber to test_cmd_code_add.py, confirmed red before the fix (failed on the exact clobbered-to-D stage entry) and green after. Full surrounding suite -- test_cmd_code_add, test_code_stage, test_code_stage_envelope, test_code_add_resolves_conflicts, test_code_add_supercharge, test_directories_feature, test_cmd_commit, test_cmd_status -- 462/462 passing, zero regressions.
Not fixed here, noted as a separate lower-severity issue: the reverse transition (tracked file replaced by a directory of the same name) crashes loudly with 'Cannot read <path>: [Errno 21] Is a directory' instead of corrupting silently -- annoying but not a data-loss risk, filed separately.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Semantic Changes
5 symbols
Files Changed
~3
1203 in snapshot
0 comments
muse hub commit comment sha256:db2a9e509b0f5de47d8d34e0aa20d1c2f8ce9ae4f9b5bf37693c125bd608579e --body "your comment"
No comments yet. Be the first to start the discussion.