Skip to content

fix(repo-hygiene): git-tree-reset aborts with fatal: ambiguous argument '@{u}' on branches with deleted upstream #393

Description

@kyle-sexton

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/skillsplugins/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

  • Resolve upstream robustly — verify the tracking ref actually exists (e.g. git rev-parse --verify --quiet '@{u}' / git rev-parse --abbrev-ref --symbolic-full-name '@{u}' and confirm it is not the literal @{u}).
  • When unresolved: emit a distinct Blocked: reason + a dedicated exit code; do not run reset or clean.
  • Dry-run surfaces the same blocked state (see companion dry-run-parity issue).
  • Add a regression test: branch with configured-but-missing upstream tracking ref.

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: highSignificant impact, or blocks an imminent release; staff this cycle.status: readyTriaged, unblocked, and fully specified; eligible to pick up.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions