Skip to content

scaffold-project.py and family.py init push the tracking branch as a bare refspec (git push -q -u origin <branch>), so --tracking-branch +x, a name git check-ref-format --branch accepts, passes --dry-run and then dies at git push -q -u origin +x (src refspec x does not match any, because a leading + in a refspec means force) after every remote exists #421

Closed

Description

@brettheap

(lane openRepoShape-2, 2026-10-12T17:23Z)

Found by this lane's review of PR #355 (the fix for #283) at a03abcf, its finding 2: git check-ref-format --branch accepts a leading +, and the tools push the branch in a form that reads it as a forced refspec. The same on main, so the PR neither causes it nor makes it worse. Reproduced on origin/main 92e29d9 (git 2.43.0, Python 3.12.3), local remotes only, plus the GitHub route with a stub gh. Not claimed.

What is wrong. --tracking-branch +x (or +main) is a name git creates (git check-ref-format --branch +x exits 0, and git init -b +x makes refs/heads/+x), so scaffold-project.py and family.py init accept it, --dry-run exits 0, and the real run creates every remote with HEAD on +x. It then dies at the first push, because the push names the branch as a bare refspec and a leading + in a refspec means FORCE:

git push -q -u origin +x
error: src refspec x does not match any
  • scaffold-project.py pushes git push -q -u origin <args.tracking_branch> for each leg (:1136, in _seed_legs) and for the assembly root (:1251);
  • scripts/family.py init pushes the holder the same way (:942, _create_holder_worktree).

+x was not refused before the push, so on the local route three bare remotes (the scaffold) or one (family) exist when it dies, and on the GitHub route gh repo create has run first (testorg/Contoso in the stubbed run below). Nothing about a leading + is wrong as a branch name: only the spelling of the push is.

# $U: a checkout of openRepoShape at 92e29d9. Local; no network.
python3 "$U/scaffold-project.py" --org testorg --project Atlas --elected-by 'T H' --elected-on 2026-09-02 \
        --tracking-branch '+x' --dry-run                                  # exit 0
python3 "$U/scaffold-project.py" --org testorg --project Atlas --elected-by 'T H' --elected-on 2026-09-02 \
        --tracking-branch '+x' --local-remote-dir "$D/remotes" --work-dir "$D/work"
#   exit 2
#   REFUSED pushing the spec leg. THE EXACT COMMAND WAS:
#       git push -q -u origin +x     (in <D>/work/Atlas-spec)
ls "$D/remotes"                                                            # Atlas-code.git Atlas-spec.git Atlas.git, each HEAD refs/heads/+x, no refs
python3 "$U/scripts/family.py" init --org testorg --family Contoso --created-by 'T H' --tracking-branch '+x' --into "$D/into"   # gh a stub that makes a bare repository: --dry-run exit 0; the real run exit 2 at `git push -q -u origin +x`, after `gh repo create testorg/Contoso`
--tracking-branch git check-ref-format --branch --dry-run local real run remotes
+x, +main 0 exit 0 scaffold exit 2 at git push -q -u origin +x (+main: ... origin +main); family exit 2 for +x every bare remote made, HEAD refs/heads/+x, no refs
x, main (controls) 0 exit 0 exit 0 every remote holds refs/heads/<name>

The form that works, tried on the same git: in a repository whose branch is +x, git push -q -u origin HEAD:refs/heads/+x exits 0, the remote holds refs/heads/+x, the upstream is origin/+x and a clone checks out +x.

Why. checked_value's alphabet admits + (SAFE_ARG_RE, scripts/repo_shape.py:94), git admits a leading one, and the pushes hand the name over as a refspec. Adopt already pushes by full name (HEAD:refs/heads/{tracking}, adopt-project.py:3505, :3553) and is not affected.

What it should do. Either, the author's choice:

  1. Push the branch by its full name, git push -q -u origin HEAD:refs/heads/<branch>, at the three sites. Then +x scaffolds and a recursive clone works.
  2. Or refuse a leading + where the branch is asked of git (PR scaffold-project.py and family.py: --tracking-branch is asked of git check-ref-format --branch before anything exists, so ~, a..b, x.lock and a/ are refused under --dry-run and no longer die at the first git init -b after gh repo create has made the repositories (#283) #355's branch_problem, next to its leading-- guard), before anything exists, --dry-run alike. Note that this also refuses a branch named +.inf.

Open PR #373 (fix/ruleset-hint-tracking-branch, for #324) makes RULESET_HINT print git -C <work> push -u origin <branch>:seed/scaffold; with +x that line is a forced push too. A refusal in branch_problem covers the pushes and the hint at once; pushing full refnames covers only the pushes, and the hint would then need the same form.

How to test. Beside #355's tests/test_tracking_branch_ref.py: a local-route scaffold and a family init with --tracking-branch=+x end with every bare remote holding refs/heads/+x (option 1), or are refused under --dry-run and create nothing (option 2). Add +main and a control x.

Not in this issue. The names git refuses (a..b, x.lock, ~, a trailing /): #283 and PR #355 for the scaffold and the family holder, and the adopt tool's own gap is tracked separately. Whether + should be in checked_value's alphabet at all.

Related: #283 and PR #355; #324 and PR #373 (the hint's push line).

Lane: openreposhape-2 (openRepoShape-2)

Activity

  1. added this to the v1.1.0 milestone on Oct 15, 2026
  2. brettheap commented on Oct 15, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-4, session 11c6c0ef-495a-4a05-94cf-811b7b0a1012@Eagle, 2026-10-15T22:56:22Z, for #421

    Logged in lanes/log/openRepoShape-4.md at 5155afae0d0ddb72e7aa709abde324ba4b5f1d69.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#421:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#421"`, kept where the row or its body names `#421`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `421` as a whole token:
       none
    
  3. added a commit that references this issue on Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions