gabriel / muse public
fix BREAKING refs dev
AI Agent gabriel · 149 days ago · Apr 27, 2026 · Diff

fix(refs): CAS guard on write_branch_ref prevents concurrent commit orphaning

write_branch_ref gains an optional expected_id parameter. When provided, the write is protected by an flock-serialised read-check-write so two concurrent callers with the same expected_id cannot both pass the check before either rename lands. Raises RefConflictError on mismatch.

commit passes parent_id as expected_id so any concurrent advance of the branch between the parent-read and the ref-write is caught and surfaced as a retryable error rather than silently orphaning a commit.

Added RefConflictError to muse.core.store and _SENTINEL sentinel for distinguishing 'no expected_id provided' from 'assert no prior ref'.

10-test regression suite in test_commit_concurrent_ref_safety.py covering CAS unit behaviour, commit branch-moved detection, and concurrent thread serialisation.

sha256:4096fc35c279dc5d153c84708ab9a3dc64d85b81be564636239935f468fcce47 sha
+28 ~14 symbols
sha256:b355666dc06cd8c935d7852156be79d758d717d60c1de1f9c7700d04ebb26121 snapshot
+28
symbols added
~14
symbols modified
0
dead code introduced
Semantic Changes 42 symbols
+ TestCommitDetectsBranchMoved class class TestCommitDetectsBranchMoved L135–221
+ test_commit_error_message_is_actionable method method test_commit_error_message_is_actionable L188–221
+ test_commit_fails_when_branch_advanced_concurrently method method test_commit_fails_when_branch_advanced_concurrently L136–176
+ test_commit_succeeds_when_branch_has_not_moved method method test_commit_succeeds_when_branch_has_not_moved L178–186
+ TestConcurrentCommitThreads class class TestConcurrentCommitThreads L233–279
+ test_only_one_of_two_concurrent_commits_wins method method test_only_one_of_two_concurrent_commits_wins L234–279
+ TestWriteBranchRefCAS class class TestWriteBranchRefCAS L67–122
+ test_cas_fails_when_expected_does_not_match method method test_cas_fails_when_expected_does_not_match L84–95
+ test_cas_fails_when_ref_is_missing_and_expected_is_set method method test_cas_fails_when_ref_is_missing_and_expected_is_set L97–104
+ test_cas_succeeds_on_first_write_when_expected_is_none method method test_cas_succeeds_on_first_write_when_expected_is_none L106–112
+ test_cas_succeeds_when_expected_matches method method test_cas_succeeds_when_expected_matches L75–82
+ test_error_contains_branch_name_and_ids method method test_error_contains_branch_name_and_ids L114–122
+ test_unconditional_write_succeeds method method test_unconditional_write_succeeds L68–73
+ _make_bare_refs function function _make_bare_refs L51–59
+ _run function function _run L42–44
+ _try function function _try L47–48
+ CliRunner import import CliRunner L34–34
+ TYPE_CHECKING import import TYPE_CHECKING L28–28
+ annotations import import annotations L24–24
+ fake_id import import fake_id L33–33
+ pathlib import import pathlib L26–26
+ pytest import import pytest L30–30
+ threading import import threading L27–27
+ write_branch_ref import import write_branch_ref L32–32
+ runner variable variable runner L39–39
~ muse/core/store.py .py 2 symbols added, 1 symbol modified
+ RefConflictError class class RefConflictError L401–404
+ _SENTINEL variable variable _SENTINEL L141–141
← Older Oldest on dev
All commits
Newer → Latest on dev

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:4096fc35c279dc5d153c84708ab9a3dc64d85b81be564636239935f468fcce47 --body "your comment"