gabriel / muse public
Open #110 Bug
filed by gabriel human · 1 day ago

muse commit's snapshot directories field goes stale when a directory's last file is removed

0 Anchors
— Blast radius
— Churn 30d
0 Proposals

muse commit's snapshot directories field goes stale when a directory's last file is removed

Found while fixing #95

While fixing #95 (empty directories left behind by switch/checkout/merge/pull), found a related but distinct bug: an ordinary muse commit that removes the last file in a directory does not update that commit's own snapshot directories field to drop the now-empty directory.

Reproduction

rm -rf /tmp/muse_stale_dirs && mkdir -p /tmp/muse_stale_dirs && cd /tmp/muse_stale_dirs
muse init --json

mkdir -p tools/plugin
echo "docs" > tools/plugin/README.md
muse code add . --json
muse commit -m "add tools/plugin" --json

rm tools/plugin/README.md
muse code add tools/ --json
muse commit -m "chore: remove stale tools/plugin" --json

# The commit's own manifest correctly excludes tools/plugin/README.md --
# but its directories field still lists it:
muse read --json --manifest | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['directories'])"
# → ['tools', 'tools/plugin']  -- WRONG, tools/plugin has zero files

Why this matters

This directly undermined the fix for #95: apply_manifest's new target_directories protection parameter is meant to let a genuinely intentional empty directory (e.g. one with a .musekeep marker) survive pruning. But if sourced from a commit's own stale directories field, it would incorrectly protect a directory that the SAME commit just emptied out — defeating the fix entirely for the fast-forward/merge path. The #95 fix worked around this by deriving target_directories from directories_from_manifest(target_manifest) (purely file-derived) everywhere instead of trusting any stored directories field — safe, but it means a genuinely intentional empty directory (one with only a .musekeep marker, no other files) is not actually protected by any of those call sites today, since directories_from_manifest cannot derive directories that have zero files.

Scope

Find where muse commit constructs the snapshot's directories field (muse/cli/commands/commit.py, plugin.snapshot() in muse/plugins/code/plugin.py) and confirm whether it's a fresh walk each time or inherits/merges with a prior value. Fix so a directory that loses its last file (and has no .musekeep marker) is correctly dropped from directories at commit time, not just left stale until some unrelated future snapshot recomputes it.

Acceptance criteria

  • The reproduction above shows directories: [] (or without tools/tools/plugin) after the second commit.
  • .musekeep-marked intentionally-empty directories are unaffected — they continue to appear in directories for as long as the marker file exists.
  • Once fixed, revisit #95's fix: the target_directories call sites that derive from directories_from_manifest(target_manifest) could then safely switch to using a loaded SnapshotRecord.directories directly where one is already in scope, correctly protecting genuinely-intentional empty directories that the current file-derived approach cannot see.
◎ Activity
●
gabriel opened this issue 1 day ago
◎
No activity yet. Use the CLI to comment.