Skip to content

scaffold-project.py holds --tracking-branch only to checked_value's alphabet, so a name git refuses as a branch (~, a..b, x.lock, a trailing /) passes --dry-run and, against GitHub, dies at the first git init -b after gh repo create has made all three repositories #283

Closed

Description

@brettheap

(lane openRepoShape-2, 2026-10-11T23:46:37Z)

Found while this lane probed --tracking-branch ~ for the YAML misread #258 reports (the ~ is the one value that misreads and is also not a branch), reproduced on main c3f4987 (git 2.43.0, Python 3.12.3). Pre-existing. Not claimed.

What is wrong. scaffold-project.py takes --tracking-branch through checked_value (scaffold-project.py:1009), which holds it to SAFE_ARG_RE (^[A-Za-z0-9._/@+~-]{1,255}$) and refuses a leading -. That alphabet is wider than the names git accepts as a branch. Nothing asks git (git check-ref-format --branch), so these pass --dry-run (exit 0) and the plan:

--tracking-branch git check-ref-format --branch --dry-run git init -b
~, a~1 128 exit 0 128 invalid initial branch name
a..b 128 exit 0 128
x.lock 128 exit 0 128
a/, /a, a//b 128 exit 0 128
.a, a. 128 exit 0 128

A real run then fails at the first git init -b <name>. With --local-remote-dir that is the first bare repository, so nothing is left. Against GitHub the order is different: _create_remotes runs gh repo create for all three repositories, and only _seed_legs runs git init -b <name> on the first working tree. The run exits 2 and the three repositories exist with no commit. The re-run is then refused scaffold-remote-exists for the two legs, which the operator must delete on GitHub first (the assembly root takes --reuse-empty-repo). The GitHub route is read from the stubbed run below and from the code; a real scaffold must not reach GitHub from a review, so none was made.

SHAPE=<openRepoShape checkout>
export GIT_AUTHOR_NAME=T GIT_AUTHOR_EMAIL=t@example.invalid GIT_COMMITTER_NAME=T GIT_COMMITTER_EMAIL=t@example.invalid
# A stand-in for gh that logs its calls and succeeds, so nothing here reaches GitHub.
STUB=$(mktemp -d); export STUB_LOG="$STUB/calls.log"
cat > "$STUB/gh" <<'EOF2'
#!/bin/sh
echo "gh $*" >> "$STUB_LOG"
[ "$1 $2" = "repo view" ] && exit 1   # no such repository yet
exit 0
EOF2
chmod +x "$STUB/gh"
for tb in '~' 'a..b' 'x.lock' 'a/'; do
  : > "$STUB_LOG"; TMP=$(mktemp -d)
  PATH="$STUB:$PATH" python3 "$SHAPE/scaffold-project.py" --org testorg --project Atlas --elected-by 'T H' \
      --elected-on 2026-09-02 --tracking-branch "$tb" --dry-run > /dev/null; dry=$?
  PATH="$STUB:$PATH" python3 "$SHAPE/scaffold-project.py" --org testorg --project Atlas --elected-by 'T H' \
      --elected-on 2026-09-02 --tracking-branch "$tb" --work-dir "$TMP/work" > "$TMP/out" 2> "$TMP/err"
  rc=$?
  echo "== --tracking-branch '$tb': --dry-run exit $dry, real run exit $rc, $(grep -c '^gh repo create' "$STUB_LOG") x gh repo create, then: $(grep -a -m1 '^    git init' "$TMP/err" | sed "s#$TMP#<TMP>#" | cut -c1-70)"
done
echo "-- the calls the last run made:"; cut -c1-70 "$STUB_LOG"

Observed on main c3f4987:

== --tracking-branch '~': --dry-run exit 0, real run exit 2, 3 x gh repo create, then:     git init -q -b ~ <TMP>/work/Atlas-spec
== --tracking-branch 'a..b': --dry-run exit 0, real run exit 2, 3 x gh repo create, then:     git init -q -b a..b <TMP>/work/Atlas-spec
== --tracking-branch 'x.lock': --dry-run exit 0, real run exit 2, 3 x gh repo create, then:     git init -q -b x.lock <TMP>/work/Atlas-spec
== --tracking-branch 'a/': --dry-run exit 0, real run exit 2, 3 x gh repo create, then:     git init -q -b a/ <TMP>/work/Atlas-spec
-- the calls the last run made:
gh repo view testorg/Atlas --json name
gh repo view testorg/Atlas-spec --json name
gh repo view testorg/Atlas-code --json name
gh repo create testorg/Atlas-spec --private --description Atlas — sp
gh repo create testorg/Atlas-code --private --description Atlas — co
gh repo create testorg/Atlas --private --description Atlas — assembl
gh api orgs/testorg --jq .plan.name

With --local-remote-dir and no stub, --tracking-branch '~' exits 2 at once (fatal: invalid initial branch name: '~', from git init -q --bare -b ~ on the first remote) and creates nothing.

What should happen. The scaffold asks git whether the name is a branch (git check-ref-format --branch <name>) next to checked_value, before any work directory or remote exists and under --dry-run too, and refuses it by name with the rule git gave. That is the same placement refuse_bad_leg_paths has for the leg paths (#179). A test per row above runs through --dry-run and asserts exit 2 with no gh repo create recorded and no directory made.

Where (main c3f4987):

  • scaffold-project.py:1009-1010: --tracking-branch through checked_value and nothing else. scripts/repo_shape.py:94 (SAFE_ARG_RE) and :1761 (checked_value).
  • scaffold-project.py:798: the local route's git init -q --bare -b; :780-815 _create_remotes, which is gh repo create only on the GitHub route; :833 _seed_legs into scripts/shape_materialize.py:737-748 git_init_commit, where git init -q -b <branch> first runs on that route; :903: the assembly root's own git init.
  • adopt-project.py:1381-1382 and scripts/family.py:454 take --tracking-branch through the same checked_value; neither was run here.

Related.

Not claimed.

🤖 Generated with Claude Code

Activity

  1. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-2, session 5b7b5f3d-dce7-452d-a33d-ab8537d91db3@Eagle, 2026-10-12T10:59:17Z, for #283

    Logged in lanes/log/openRepoShape-2.md at 46554f34afdb41770257539ab53eb4cf1ff40e3d.

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

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#283:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#283"`, kept where the row or its body names `#283`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `283` as a whole token:
       none
    
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

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions