gabriel / musehub public
Open #220 Bug
filed by gabriel human · 29 days ago

muse hub repo create seeds every new repo with an invalid, snapshot-less genesis commit (initialize=True by default) — every first push needs --force

0 Anchors
— Blast radius
— Churn 30d
0 Proposals
No description provided.
◎ Activity1
●
gabriel opened this issue 29 days ago
○
gabriel 29 days ago

Found while writing Episode 01's PUSH section — a question about why origin (not a real remote) needed replacing with local led to "why does the first push need --force at all," which led here. Verified on both local and staging, not local-instance noise.

Part 1 — the actual bug: every new repo gets a broken genesis commit

Repro (identical result on https://localhost:1337 and https://staging.musehub.ai, two different never-before-used repo names):

muse init && <add + commit real content>
muse hub repo create --name <matches-dir-name> --visibility public
muse push local main
# ❌ Push rejected — remote 'local/main' has diverged.
#    Pull first (muse pull) or use --force to override.

Confirmed this is not a real divergence — muse cloneing the freshly-created (never pushed-to) repo directly surfaces the real problem:

⚠️ apply_mpack: malformed commit — skipped: snapshot_id must be a
   non-empty str, got ''
✅ Cloned into '...' — 0 commit(s), 0 blob(s), domain=code, branch=main
   (sha256:c4521a91...)

Root cause — musehub/services/musehub_repository.py:255-277, the if initialize: block in create_repo():

init_commit = MusehubCommit(
    commit_id=init_commit_id,
    branch=default_branch,
    parent_ids=[],
    message="Initial commit",
    author=owner_user_id,
    timestamp=now,
    committed_at_raw=now.isoformat(),
)

No snapshot_id is ever set, and no corresponding MusehubSnapshot row is ever created. The commit record is structurally invalid the moment it's written — there is no valid snapshot for it to reference. Any real client that tries to materialize it (clone, or push's divergence check comparing local history against this phantom remote tip) correctly refuses to treat it as real, and push's three-way comparison — which has no way to know the "existing" remote history is garbage rather than real prior work — reports a divergence and demands --force.

Why this matters: initialize: bool = Field(True, ...) (musehub/models/musehub.py:168) — this is the default for POST /api/repos. Every repo created via muse hub repo create without an explicit override hits this path. This has almost certainly affected every fresh demo repo built across this entire video series (and likely most real usage) — --force was silently treated as an expected quirk of first pushes for a long time before this got traced to an actual root cause.

Suggested fix for Part 1: when initialize=True, actually create a real empty MusehubSnapshot (empty manifest) and set commit.snapshot_id to reference it, so the seeded "Initial commit" is a structurally valid, materializable commit like any other — preserving the "immediately browsable" intent the docstring describes for whatever caller wants it (this reads like a web-UI "create repo" wizard feature, per the surrounding "Wizard behaviors" docstring grouping it with license/topics/template_repo_id).

Part 2 — separate recommendation: CLI default shouldn't request initialize at all

Even once Part 1 is fixed, muse hub repo create (the CLI command) should not be requesting initialize=True in the first place. The API's default make sense for whatever web UI "create a new repo from scratch" flow this was built for — someone might want something browsable before writing any local code. But the CLI's hub repo create is used almost exclusively in the opposite workflow: real local content already exists and is about to be pushed. A seeded placeholder commit there is pure friction, not a feature — every first push this season had to reach for --force because of it.

Suggested fix for Part 2:

  • muse hub repo create (CLI) should explicitly send initialize=false in its request body, overriding the API's default for this specific caller — don't change the API's own default, since other callers (the web UI wizard) may genuinely want initialize=True's behavior once it's fixed.
  • Optionally add an explicit --initialize opt-in flag to the CLI for the rare case someone wants a browsable-before-first-push repo.

Both parts verified independently — Part 1 reproduces even if Part 2 is never done (the bug exists regardless of which caller requests initialize=True); Part 2 is worth doing regardless of how quickly Part 1 gets fixed, since it removes the friction from the CLI workflow immediately.

Deferred per usual until after the season wraps.