fix(clone,pull): never claim success when a snapshot hash mismatch silently drops commits (muse#35)
rc11 report: a fresh `muse clone` of a repo with a corrupted snapshot delta exited 0, printed "Cloned into ... 0 commit(s)", and left the working tree empty -- then a follow-up `muse pull` reported "Already up to date" and never recovered. User-reported as still unresolved; confirmed it still reproduces on 0.2.1rc12 before touching anything.
Root cause chain, all three links now fixed:
1. muse/core/mpack.py — apply_mpack's snapshot-writing loop: when _apply_snapshot_deltas's delta-reconstruction fails (hash mismatch, invalid object IDs, unresolvable parent chain), the snapshot ID was simply absent from `resolved_manifests` and the main loop did a bare `continue` -- never recording it in `skipped_snapshot_ids`, which was only ever populated by the SEPARATE "references a failed blob object" check. The returned ApplyResult.skipped_snapshots stayed empty even though a snapshot (and every commit depending on it) had just been silently dropped -- no record anywhere that anything had gone wrong.
2. muse/cli/commands/clone.py — the branch-ref-writing loop had a comment literally saying "Only advance refs for branches whose tip commit landed cleanly," but never implemented that check: write_branch_ref and set_remote_head were called unconditionally for every branch the server advertised, regardless of whether apply_mpack actually wrote that branch's tip commit. This is THE bug: the local branch ref ends up pointing at a commit that doesn't exist in the store (0 commits, muse log shows nothing) while `muse status`/`muse pull` believe everything is fine. Also fixed total_skipped/exit_code to account for skipped_snapshots and commits the server sent but which were never written (previously only blob-level failures counted, so a 0-commit clone with 0 corrupted blobs still reported "cloned" success).
3. muse/cli/commands/pull.py — same unconditional-advance bug for the remote-tracking ref: set_remote_head ran whenever `failed_blobs` was empty, with no check for skipped_snapshots or whether the commit was actually written. THIS is what made the corruption permanent: once the tracking ref wrongly equals the remote's head, the next pull's Phase 1 short-circuit ("already_known == remote_commit_id" -> skip fetch entirely) means the skipped commit/snapshot can never be re-requested -- exactly the ticket's "follow-up pull does not repair the skipped state" symptom. Both clone.py and pull.py's new guards defensively treat a malformed/legacy-format commit ID as "not written" rather than letting validate_object_id's ValueError propagate and abort the whole command.
9 new/updated tests: tests/test_apply_mpack_partial_failure.py gains PF-5 (hash-mismatch skip is recorded in skipped_snapshots) and PF-5b (a retry with the correct delta for the same snapshot_id succeeds -- proving the skip is recoverable, satisfying the ticket's "follow-up pull can recover" criterion at the apply_mpack layer). tests/test_cmd_clone_hardening.py gains TestSnapshotHashMismatchGuard (3 tests: ref withheld + PARTIAL exit when the commit is missing, JSON schema gains skipped_snapshots/ commits_missing fields, happy-path regression guard). tests/test_cmd_pull_hardening.py gains TestTrackingRefNotAdvancedOnSkippedSnapshot (2 tests). Two pre-existing tests updated: test_clone_partial.py's skipped_blobs assertion widened from an exact count to >=1 now that cascading snapshot/commit skips are counted too (the old exact-1 assertion described the bug's own undercounting), and test_cmd_clone_hardening.py's test_branch_refs_written_for_every_remote_branch (which mocked apply_mpack without ever writing real commits) now also mocks read_commit to simulate the happy path explicitly, since its old assertion was testing the unconditional-write bug itself.
Confirmed via genuine RED: reverted all three fixes in turn, re-ran the new tests -- the clone.py revert reproduced the ticket's exact bug (branch ref written pointing at the missing commit ID) -- then restored and confirmed GREEN. 579-test regression sweep across every mpack/clone/pull-relevant suite passes with zero regressions. Live-verified the normal (non-corrupted) clone/push/pull path end-to-end against a real local MuseHub server: 2 commits pushed, cloned cleanly, working tree restored, log/status correct.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Semantic Changes
18 symbols
Files Changed
~7
1252 in snapshot
0 comments
muse hub commit comment sha256:a5f8a020b53ddab68f231cf27489fe865525f85ee924133034262d39b332076e --body "your comment"
No comments yet. Be the first to start the discussion.