From 53746cfeb1ad0df5b885c408cbfffd6f49fd4bb4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 13:59:19 +0000 Subject: [PATCH] fix(branch_sweep): stop reporting origin/HEAD as an unmerged branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first live run listed a branch named `origin` under KEEP with verdict UNKNOWN. No such branch exists: `refs/remotes/origin/HEAD` is a symbolic ref, and git renders its `%(refname:short)` as bare `origin` — which survives both the `origin/` strip and the `!= HEAD` guard, so it fell through to branch_contribution as `origin/origin`, came back UNKNOWN, and was filed as outstanding work. Nothing was ever at risk: UNKNOWN is fail-safe and never enters the delete set. The cost is honesty. It reported PyAutoBrain as having 3 unmerged branches when it has 2, and invited a reader to go looking for work that does not exist — a null result dressed as a finding, which is the D1 mistake docs/agent_failure_modes.md already names. Iterating full refnames and stripping the real prefix removes the ambiguity at the source rather than special-casing the rendered string. The regression test was checked the only way worth trusting: it fails against the old iteration and passes against the new one. It also pins the count, so a future phantom entry fails the assertion rather than quietly inflating a total nobody re-derives. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KwqicJpMqmcT5RVbyNwdKq --- bin/branch_sweep.sh | 8 +++++++- tests/test_branch_sweep.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/bin/branch_sweep.sh b/bin/branch_sweep.sh index dcd9aedb..a39d955d 100755 --- a/bin/branch_sweep.sh +++ b/bin/branch_sweep.sh @@ -182,7 +182,13 @@ while read -r b; do fi ;; *) keep+=("$b $word") ;; # UNKNOWN is never safe esac -done < <(g for-each-ref --format='%(refname:short)' refs/remotes/origin | sed 's|^origin/||') + # Full refnames, not `:short`. Git abbreviates refs/remotes/origin/HEAD to + # bare `origin`, which survives an `origin/` strip and a `!= HEAD` guard, + # then reads as a branch named "origin" with verdict UNKNOWN — a symbolic + # ref rendered as an unmerged branch. Harmless (UNKNOWN is never deletable) + # but it is a null result dressed as a finding, which is the D1 mistake. +done < <(g for-each-ref --format='%(refname)' refs/remotes/origin \ + | sed 's|^refs/remotes/origin/||') echo echo "Branch sweep — $OWNER/$NAME (mode: $MODE, base: $BASE)" diff --git a/tests/test_branch_sweep.py b/tests/test_branch_sweep.py index 0f2a52c4..28922c8b 100644 --- a/tests/test_branch_sweep.py +++ b/tests/test_branch_sweep.py @@ -130,6 +130,22 @@ def test_audit_classifies_every_branch(world): assert "archive/condemned/something\tgut-transit-ref" in out +def test_symbolic_head_is_not_reported_as_a_branch(world): + """refs/remotes/origin/HEAD must not surface as a branch called "origin". + + Git abbreviates that ref to bare `origin`, which slips past an `origin/` + strip and a `!= HEAD` guard and then lands in KEEP with verdict UNKNOWN — + a symbolic ref rendered as unmerged work. It was never deletable, so the + cost is a wrong count and a reader misled about what is outstanding. + """ + clone, _, bin_dir = world + out = _sweep(clone, bin_dir).stdout + assert "origin\tUNKNOWN" not in out + assert "\n origin\t" not in out + # the real answer for this fixture, with nothing invented alongside it + assert "1 unmerged" in out + + def test_audit_never_deletes(world): clone, origin, bin_dir = world before = _git(origin, "for-each-ref", "--format=%(refname)", "refs/heads")