muse pull does not prune stale remote tracking refs after hub-side branch deletion
Summary
When a merge proposal is merged on the hub with 'Source branch deleted', the tracking ref under .muse/remotes/<remote>/<branch> is not removed by the subsequent muse pull. The user must run muse fetch <remote> --prune as a separate explicit step.
Steps to reproduce
- Push a branch and create an MP
- Merge the MP on the hub (source branch auto-deleted)
muse switch dev && muse pull local devtree .muse/remotes— stale tracking ref still present
Expected behavior
muse pull local dev should behave as muse fetch local --prune && muse merge local/dev — pruning any tracking refs for branches the remote no longer reports. At minimum, the MP merge response (which confirms source branch deletion) should trigger a prune of that specific ref.
Actual behavior
Stale tracking refs accumulate silently. The only way to clear them is an explicit muse fetch <remote> --prune.
Context
This surfaced during a remote branch cleanup session. After merging MP #8 into local dev, .muse/remotes/local/fix/proposal-closed-state-badge persisted until manually pruned.
Implemented and shipped — commit ec9f1f47 on dev/main (pushed to local/staging/production). Implements exactly the fetch.prune config option proposed in the comment above, plus fixes the actual root cause the ticket reported.
Root cause
muse fetch --prune already existed and worked correctly. The bug was that muse pull had its own, entirely independent fetch implementation that never called the pruning routine at all — so even running muse fetch <remote> --prune immediately before a muse pull didn't help, since the very next muse pull would leave stale refs behind again with no way to avoid it short of remembering to run fetch --prune as a constant separate habit.
What shipped
fetch.prune config (muse/cli/config.py), mirroring git config fetch.prune exactly:
muse config set fetch.prune true # this repo only
muse config set --global fetch.prune true # every repo on this machine
Repo-level setting wins over global when both are set; defaults to False (pruning stays opt-in, same as git). --prune/--no-prune remain available on both muse fetch and muse pull as one-shot overrides regardless of config.
muse config get/set --global — new, generic flag mirroring git config --global: reads/writes ~/.muse/config.toml instead of the repo config, works outside a repo too. (This didn't exist before at all — needed to make the ticket's own proposed global variant possible.)
muse pull now actually prunes. Calls the same _prune_stale_refs() routine muse fetch --prune already used, right after discovering the remote's live branches — before resolving the branch being pulled, so a hub-deleted branch's stale ref is cleared even when pulling a completely different, still-existing branch. This is the exact repro from the ticket: MP merged with "source branch deleted", then muse pull local dev (a different branch) now correctly clears .muse/remotes/local/<deleted-branch>.
New pruned: list[str] field on muse pull --json's output (matching muse fetch's existing schema), populated at every status outcome. Pruning respects --dry-run (previews only, writes nothing).
Tests
New tests/test_pull_fetch_prune_config.py (13 tests): get_fetch_prune precedence (repo wins, global fallback, default false), muse config --global read/write/isolation from repo config, muse fetch honoring the config with --no-prune override, and muse pull actually pruning — explicit --prune, config-driven (no flag needed), default-off, and --dry-run preview-only.
Confirmed genuine RED by shelving the entire implementation and re-running: all 13 failed (ImportError for the new config function, "Unknown config namespace 'fetch'", argparse errors for the new flags, missing pruned JSON key) — restored, all green. One test-authoring mistake caught along the way (not a product bug): three pull tests initially included the "stale" branch inside the mocked remote's own live branch list, which made it not stale at all — fixed to correctly omit it.
Regression: 456 tests across the fetch, pull, and config test suites — all green, no regressions.
Proposed solution:
fetch.pruneconfig optionMirror git's
fetch.prunebehavior exactly.Config (set once, applies to all fetches in that repo):
Behavior when set:
muse fetch <remote>— prunes stale tracking refs automaticallymuse pull <remote> <branch>— prunes as part of the fetch stepBehavior when unset (default): current behavior — no pruning, explicit
muse fetch <remote> --prunerequired.Global variant (applies across all repos on the machine):
This is the exact pattern git uses (
git config --global fetch.prune true). Agents and humans who set it once never have to think about stale tracking refs again. The--pruneflag onmuse fetchstays as an explicit one-shot override regardless of config.