gabriel / musehub public
fix patch fix/220-repo-init-commit-cloneable #1 / 1
AI Agent gabriel · 2 days ago · Oct 22, 2026 · Diff

fix: create_repo seeds invalid genesis commit; commit_files_to_repo's commit_id not client-verifiable (musehub#220)

Audit (Part 1, as reported): create_repo(initialize=True) hand-rolled a commit_id with no snapshot_id and no MusehubSnapshot row -- structurally invalid the moment any client tried to clone it ("apply_mpack: malformed commit -- snapshot_id must be a non-empty str"). Push's divergence check, comparing local history against this phantom remote tip, correctly reported a divergence and demanded --force -- every first push this season needed it because of this.

Investigation found a second, bigger bug while building the fix: reusing commit_files_to_repo (the natural way to create a correct empty commit) surfaced that its own commit_id computation -- a hand-rolled hash seeded from repo_id/branch/snapshot_id/message/timestamp -- does not match what a real client recomputes via hash_commit() on clone/fetch (muse/core/commits.py::_verify_commit_id, called on every write_commit, including clone/pull). Confirmed computationally: identical field values produce different hashes. commit_files_to_repo is shared by org creation, org membership changes, and identity-repo creation at registration -- every commit ever created through it would fail content-hash verification the moment any real client tried to store it locally.

Design decision (confirmed via AskUserQuestion): fix commit_files_to_repo's root cause rather than hand-rolling a separately-correct commit for create_repo only -- reusing the still-broken function would just inherit the bug, and the broader issue is real and already live on staging.

Fix: - commit_files_to_repo now computes commit_id via muse.core.ids.hash_commit (the canonical, client-verifiable formula), not a hand-rolled hash. - create_repo(initialize=True) now calls commit_files_to_repo with an empty files dict instead of hand-rolling a commit with no snapshot -- produces a correct, materializable empty commit with a real MusehubSnapshot row.

TDD: wrote tests/test_repo_init_commit_cloneable.py first (5 tests) -- non-empty snapshot_id, snapshot_id resolves to a real MusehubSnapshot row, commit_id matches what _verify_commit_id's real recomputation would produce (reproduces the actual client-side check), commit_files_to_repo's own commit_id passes the same check independent of create_repo, and initialize=False still creates zero commits. Confirmed genuine RED via muse-dev shelf save/pop isolation on both files.

Regression: 217 passed across repo-creation/identity/org/membership test files -- no regressions in any flow that uses commit_files_to_repo.

Verified live end-to-end against a local MuseHub instance, reproducing the ticket's exact repro steps: muse init + commit + `muse hub repo create` (no --initialize, new CLI default -- muse-side companion fix) + `muse push local main` succeeded with no --force needed and no divergence error; separately, `muse hub repo create --initialize` + `muse clone` on the empty seeded repo succeeded cleanly (1 commit, 0 blobs, no malformed-commit warnings).

Co-Authored-By: Claude Sonnet 5 <[email protected]>

sha256:5dbc6b75b9add84f8812f9f390fbd5b47fcf9e22947b2cf86ae54146c99c9df8 sha
+23 ~2 symbols
3 changed · 1108 in snapshot files
sha256:452e1b7cc381ee2404ccd77077bcbd17dba55b9f7ad8346e9276d15c3ed538bb snapshot
+23
symbols added
~2
symbols modified
3
files changed
1108
files in snapshot
0
dead code introduced
Semantic Changes 25 symbols
+ _COUNTER variable variable _COUNTER L40–40
+ _IDENTITY_ID variable variable _IDENTITY_ID L39–39
+ _OWNER variable variable _OWNER L38–38
+ _recompute_commit_id function function _recompute_commit_id L48–57
+ _uid function function _uid L43–45
+ AsyncSession import import AsyncSession L28–28
+ MusehubCommit import import MusehubCommit L32–32
+ MusehubCommitRef import import MusehubCommitRef L32–32
+ MusehubSnapshot import import MusehubSnapshot L32–32
+ annotations import import annotations L23–23
+ commit_files_to_repo import import commit_files_to_repo L34–34
+ compute_identity_id import import compute_identity_id L31–31
+ create_repo import import create_repo L33–33
+ datetime import import datetime L25–25
+ hash_commit import import hash_commit L30–30
+ pytest import import pytest L27–27
+ pytestmark variable variable pytestmark L36–36
+ test_rc01_initialize_true_commit_has_nonempty_snapshot_id function async_function test_rc01_initialize_true_commit_has_nonempty_snapshot_id L60–79
+ test_rc02_snapshot_id_resolves_to_real_snapshot_row function async_function test_rc02_snapshot_id_resolves_to_real_snapshot_row L82–103
+ test_rc03_initialize_true_commit_id_is_client_verifiable function async_function test_rc03_initialize_true_commit_id_is_client_verifiable L106–132
+ test_rc04_commit_files_to_repo_produces_client_verifiable_commit_id function async_function test_rc04_commit_files_to_repo_produces_client_verifiable_commit_id L135–167
+ test_rc05_initialize_false_creates_no_commit function async_function test_rc05_initialize_false_creates_no_commit L170–184
~ musehub/services/musehub_sync.py .py 1 symbol added, 1 symbol modified
+ hash_commit import import hash_commit L26–26
Files Changed
+1 ~2
1108 in snapshot
← Older Oldest on fix/220-repo-init-commit-cloneable
All commits
Newer → Latest on fix/220-repo-init-commit-cloneable

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:5dbc6b75b9add84f8812f9f390fbd5b47fcf9e22947b2cf86ae54146c99c9df8 --body "your comment"