Summary
git-tree-reset.sh fails with fatal: ambiguous argument '@{u}' and performs a partial destructive operation when run on a branch whose upstream remote-tracking ref no longer exists (e.g. a feature branch whose PR was merged and the remote branch deleted).
Component
plugins/repo-hygiene/skills/clean/skills — plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.sh (the tree action of /repo-hygiene:clean).
Root cause
Line 95:
UPSTREAM="$(git rev-parse --abbrev-ref '@{u}' 2>/dev/null | tr -d '\r' || true)"
When a branch has an upstream configured (branch.<name>.remote + .merge) but the corresponding remote-tracking ref refs/remotes/<remote>/<branch> is absent (deleted on the remote after a squash-merge), git rev-parse --abbrev-ref '@{u}' emits the literal string @{u} instead of a resolved ref name and exits 0. The non-empty guard at line 96 therefore passes, and UPSTREAM is set to the literal @{u}.
At line 184 git reset --hard "$UPSTREAM" runs git reset --hard @{u} → fatal: ambiguous argument '@{u}': unknown revision or path not in the working tree. The reset does not happen.
Evidence (this session)
Run across 16 repos; 6 feature-branch repos hit this exact failure (upstream branches deleted after merge), each printing:
Upstream: @{u}
fatal: ambiguous argument '@{u}': unknown revision or path not in the working tree.
Affected branches included feat/babysit-prs-convergence, feat/clean-remove-path-helper, fix/disk-hygiene-audit-findings, feat/session-flow-reanchor, refactor/retire-codex-babysit, chore/trim-observer-preflight-post-decommission.
Impact
- Confusing hard-
fatal mid-run.
- Partial op: reset is skipped but
git clean -fdx still runs (see companion issue on the missing error guard), so the tree gets cleaned without being reset — not the advertised "fresh-pull" semantics.
Expected
Detect an unresolvable upstream and treat it as a first-class gate: skip the repo with a clear Blocked: upstream-unresolved (<remote>/<branch> gone — nothing to reset to) message and exit non-zero, rather than attempting a reset against the literal @{u}.
Acceptance criteria
Summary
git-tree-reset.shfails withfatal: ambiguous argument '@{u}'and performs a partial destructive operation when run on a branch whose upstream remote-tracking ref no longer exists (e.g. a feature branch whose PR was merged and the remote branch deleted).Component
plugins/repo-hygiene/skills/clean/skills—plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.sh(thetreeaction of/repo-hygiene:clean).Root cause
Line 95:
UPSTREAM="$(git rev-parse --abbrev-ref '@{u}' 2>/dev/null | tr -d '\r' || true)"When a branch has an upstream configured (
branch.<name>.remote+.merge) but the corresponding remote-tracking refrefs/remotes/<remote>/<branch>is absent (deleted on the remote after a squash-merge),git rev-parse --abbrev-ref '@{u}'emits the literal string@{u}instead of a resolved ref name and exits 0. The non-empty guard at line 96 therefore passes, andUPSTREAMis set to the literal@{u}.At line 184
git reset --hard "$UPSTREAM"runsgit reset --hard @{u}→fatal: ambiguous argument '@{u}': unknown revision or path not in the working tree.The reset does not happen.Evidence (this session)
Run across 16 repos; 6 feature-branch repos hit this exact failure (upstream branches deleted after merge), each printing:
Affected branches included
feat/babysit-prs-convergence,feat/clean-remove-path-helper,fix/disk-hygiene-audit-findings,feat/session-flow-reanchor,refactor/retire-codex-babysit,chore/trim-observer-preflight-post-decommission.Impact
fatalmid-run.git clean -fdxstill runs (see companion issue on the missing error guard), so the tree gets cleaned without being reset — not the advertised "fresh-pull" semantics.Expected
Detect an unresolvable upstream and treat it as a first-class gate: skip the repo with a clear
Blocked: upstream-unresolved (<remote>/<branch> gone — nothing to reset to)message and exit non-zero, rather than attempting a reset against the literal@{u}.Acceptance criteria
git rev-parse --verify --quiet '@{u}'/git rev-parse --abbrev-ref --symbolic-full-name '@{u}'and confirm it is not the literal@{u}).Blocked:reason + a dedicated exit code; do not run reset or clean.