Data integrity: harmony auto-apply reports 'auto-resolved' but silently discards the recorded resolution's content
Summary
muse merge's harmony auto-apply path (--harmony-autoupdate, on by default) reports success (✔ [harmony] auto-resolved: <path>) and increments the resolution's applied_count, but the actual merged content it writes is not the recorded resolution's outcome_blob — it silently falls back to whichever side happens to be "ours", discarding the explicit, human-verified decision that was supposedly just replayed.
This is a data-integrity bug, same tier as the zero-byte-file/code-domain issue from earlier this project (see musehub staging history) — the CLI actively reports a success message describing an action that did not actually happen.
Reproduction (confirmed twice, from a clean repo each time)
Two branches diverge from a common base, each changing
calc.py::add's body differently (return a + b + 1vsreturn a + b + 100).Merge produces a real, correct, symbol-scoped conflict (
CONFLICT (both modified): calc.py::add).Manually resolve to a third value,
return a + b + 101, viamuse resolve calc.py::add+muse commit.Confirm harmony recorded it correctly:
$ muse harmony show <pattern-id> --json { "resolutions": [{ "strategy": "manual", "confidence": 1.0, "human_verified": true, ... }] }The resolution's own
outcome_blob, checked directly viamuse cat-object, correctly containsreturn a + b + 101.Reproduce the identical conflict (same blob fingerprint) on a fresh pair of branches from the same original base.
muse merge:✔ [harmony] auto-resolved: calc.py::add Merge made by the three-way strategy.No conflict markers. Looks successful.
But the actual resulting file (working tree and the committed snapshot, both checked — this is not a working-tree-refresh artifact) is:
def add(a, b): return a + b + 1— i.e. exactly "ours" from step 5, byte for byte (the committed object ID matches the recorded pattern's own
ours_idfield exactly). Not+101. The recorded resolution was never actually applied.muse harmony showafterward confirmsapplied_countincremented to 1 on the resolution that was --- per the file contents --- never used.
Why this matters
This is the mechanism the whole "Harmony learns from you" pitch rests on
(see docs/agent-guide.md's Harmony section, and the muse docs generally).
If auto-apply silently keeps "ours" instead of the taught resolution:
- Any agent or human trusting the "auto-resolved" message and moving on
without inspecting the file has just committed the wrong merge result,
with no error, no warning, and a false confidence increment
(
applied_count) implying the system worked correctly. - This defeats the entire point of recording resolutions in the first place --- the second (and every subsequent) occurrence of a taught conflict pattern produces silently incorrect output.
Hypothesis (not confirmed)
The auto-apply path likely finds the correct pattern/resolution (matching
fingerprint), correctly decides not to surface a conflict, but the actual
snapshot-write step defaults to the merge engine's ordinary "ours" branch
of logic instead of writing read_object(repo_root, resolution.outcome_blob)
into the merged manifest at that address. Needs an engineer tracing
harmony_auto_apply (referenced from muse/cli/commands/merge.py) to
confirm exactly where the resolution's content gets dropped.
Ask
- Root-cause exactly where
harmony_auto_apply's selected resolution fails to make it into the merged manifest. - Fix so the merged file's content actually matches
outcome_blob, not "ours". - Add a regression test: record a resolution whose
outcome_blobdiffers from both "ours" and "theirs", reproduce the same conflict pattern again, and assert the merged file's content equals the resolution'soutcome_blob--- not either original side. (The existing test suite apparently doesn't already cover this, or this would have been caught.) - Audit whether the same gap exists in
muse hub proposal merge(server-side merge), which perrun_merge's own docstring shares this exact code path with the local CLI.
Priority
Higher than #91/#92 (found the same week, same project) --- those were either non-blocking classification quirks or a narrow false-conflict edge case. This one falsely reports success while discarding a human's explicit, recorded decision. Recommend treating as a data-integrity bug, not a backlog item, even though the season's episode 09 script doesn't have to wait for the fix (see the episode's own production notes for how it's being handled).
Related
- #91, #92 --- found the same week, different root causes, lower severity.
Source
Found by Claude Sonnet 5 while researching Episode 09 (Harmony), 2026-09-12.
Root-caused, fixed, and shipped to source.
Root cause (confirmed via the exact repro in this ticket, reproduced live twice via
muse-devbefore touching any code):auto_apply()(muse/core/harmony/engine.py) correctly returns{symbol_path: outcome_blob}— for the code domain,symbol_pathis a symbol address like"calc.py::add", not a plain file path. This part was already correct and already tested (TestAutoApplySymbolPathsintests/test_harmony_integration.py).The actual bug was one level up, in
muse/cli/commands/merge.py:This inserted a bogus
"calc.py::add"entry into a dict that's supposed to be keyed by plain file paths, while the real"calc.py"entry stayed at its stale pre-harmony value (which defaults to "ours"). That stale manifest is what later gets written to the working tree and committed — exactly matching every symptom in this ticket: the success message, theapplied_countbump, and the silently-wrong content in both the working tree and the committed object.Fix: one line —
merged_files.update({p.split("::")[0]: oid for p, oid in harmony_resolved.items()}). Matches the identicaladdr.split("::")[0]convention already used byrun_merge's own plugin-conflict path and byauto_applyitself elsewhere.Ask #3 (regression test):
tests/test_harmony_merge_commit_integrity.pyreproduces your exact scenario end-to-end through the real CLI — two branches conflict on the same symbol, resolved manually to a third value distinct from both "ours" and "theirs", harmony confirmed to have recorded it, then the identical conflict pattern reproduced on a fresh branch pair. Asserts the merge reports"merged"and checks the actual committed object (viamuse cat/muse cat-object, not just the working tree) matches the recorded resolution. Confirmed genuine RED against the unfixed code first. 797-test harmony+merge regression suite: zero regressions.Ask #4 (musehub audit): not applicable.
musehub/services/proposal_merge_strategies.py::execute_merge_strategycallsrun_merge(...)without passing aplugin— sorun_merge's symbol-level/harmony branch is never reached; musehub's merge strategies operate exclusively on plain file-path-keyed manifests. Harmony'sauto_applyis never invoked anywhere in musehub (confirmed viamuse code grep "auto_apply"— zero matches outside docs). No counterpart bug exists there today; worth re-checking if musehub ever starts passing apluginfor symbol-level server-side merges.Not yet in a published release — same batching note as the other tickets this session.
Closing.