Skip to content

assembly-root bootstrap.py: a lone surrogate or NUL in tracking_branch (or a leg's branch:) ends make bootstrap in a subprocess traceback in step (a), and validate-manifest.py says manifest ok, so no tool names it #455

Closed

Description

@brettheap

Lane: openreposhape-5 (openRepoShape-5)

(lane openRepoShape-5, 2026-10-12T19:25:29Z)

Found by lane 5's review of fix/bootstrap-print-name-escaped (the branch for #391), its F2, and reproduced on origin/main 53bee5b. The NUL row is lane 5's own addition while reproducing it. Not claimed. It is the sibling of #391, with a different mechanism: #391's value ends the run in a print, this one in an argument handed to git, and in both the run ends one step before the validator that could name the problem.

What is wrong. Step (a) of templates/assembly-root/scripts/bootstrap.py places each leg on its tracking branch. The branch is --branch, else project.yaml's tracking_branch, else main, and a leg's own branch: wins over all three (_checkout_all_tracking_branches, str(leg.get("branch") or branch)). It reaches git as argv (rev-parse --verify --quiet refs/heads/<branch>, checkout -q -b <branch> <pin>) through _try_git, which catches Refusal and nothing else:

def _try_git(args: list[str], cwd: Path) -> str | None:
    try:
        return git_out(args, cwd=cwd)
    except Refusal:
        return None

subprocess cannot encode a lone surrogate into an argument (UnicodeEncodeError) and refuses a NUL (ValueError: embedded null byte). Neither is a Refusal, so the run ends in a traceback from subprocess.py through repo_shape.git_out, exit 1 (make bootstrap: 2), with (a) legs on tracking branches printed and nothing after it. A branch git merely refuses (a\x1bb) is handled: _try_git returns None and the leg says could not create branch ...; leaving detached.

No tool names the value. validate-manifest.py never reads tracking_branch or a leg's branch (there is no branch in the file), so it prints manifest ok and exits 0 for both. Compare #280: a name holding a lone surrogate is now a finding (manifest-name, #390), which is the treatment these two keys lack.

SHAPE=<checkout of origin/main>
STUB=$(mktemp -d); printf '#!/bin/sh\nexit 97\n' > "$STUB/gh"; chmod +x "$STUB/gh"
export PATH="$STUB:$PATH" GH_TOKEN=bogus GH_CONFIG_DIR=$(mktemp -d)
export GIT_AUTHOR_NAME=T GIT_AUTHOR_EMAIL=t@example.invalid GIT_COMMITTER_NAME=T GIT_COMMITTER_EMAIL=t@example.invalid
D=$(mktemp -d)
python3 "$SHAPE/scaffold-project.py" --org testorg --project Atlas --elected-by 'T H' --elected-on 2026-09-02 \
    --local-remote-dir "$D/remotes" --work-dir "$D/work" > /dev/null
git -c protocol.file.allow=always clone -q --recurse-submodules "$D/remotes/Atlas.git" "$D/Atlas"
cd "$D/Atlas" || exit 1
for value in main 'a\\ud83db' 'a\\x00b'; do
    sed -i "s/^tracking_branch: .*/tracking_branch: \"$value\"/" project.yaml
    grep '^tracking_branch' project.yaml
    python3 scripts/validate-manifest.py | tail -n 1; echo "validator exit ${PIPESTATUS[0]}"
    python3 scripts/bootstrap.py > /dev/null 2> "$D/err"; echo "bootstrap exit $?"; tail -n 1 "$D/err"
    make bootstrap > /dev/null 2>&1; echo "make bootstrap exit $?"
done
sed -i 's/^tracking_branch: .*/tracking_branch: main/' project.yaml
sed -i '0,/^    path: spec$/s//    path: spec\n    branch: "a\\ud83db"/' project.yaml
python3 scripts/validate-manifest.py | tail -n 1; python3 scripts/bootstrap.py > /dev/null 2> "$D/err"; echo "exit $?"; tail -n 1 "$D/err"

Observed on origin/main 53bee5b, Python 3.12.3:

project.yaml validate-manifest.py scripts/bootstrap.py
tracking_branch: main (control) manifest ok: Atlas (atlas), 3 legs, exit 0 exit 0
tracking_branch: "a\ud83db" manifest ok: Atlas (atlas), 3 legs, exit 0 exit 1, UnicodeEncodeError: 'utf-8' codec can't encode character '\ud83d' in position 12: surrogates not allowed, from subprocess.py via repo_shape.git_out (:872), bootstrap.py _try_git (:95), checkout_tracking_branch (:115) and _checkout_all_tracking_branches (:349)
tracking_branch: "a\x00b" the same, exit 0 exit 1, ValueError: embedded null byte
a leg's branch: "a\ud83db" (tracking_branch: main) the same, exit 0 exit 1, the same UnicodeEncodeError, :349

make bootstrap exits 2 for both tracking_branch failing rows (make: *** [Makefile:44: bootstrap] Error 1); it was not run for the leg-branch row. The bootstrap.py frames are the template's own lines on origin/main, copied into the project.

What it should do. Two halves, and the author decides whether both belong in one change:

How to test. In a scaffolded project (the project fixture), edit tracking_branch and then a leg's branch to "a\ud83db" and to "a\x00b", as bytes. Run validate-manifest.py alone (a FINDING naming the key, exit 1, no traceback, once the first half lands) and scripts/bootstrap.py (exit 1 from the validator, no Traceback, bootstrap FAILED: manifest (exit 1), and under the second half alone, could not create branch on each leg). make bootstrap exits 2. A plain main stays byte-identical.

Not in this issue.

Related: #391, #280, #283.

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-15T23:06:09Z, for #455

    Logged in lanes/log/openRepoShape-4.md at 6b1715ef47ea9ed7a0ceb0c926631b45565eba38.

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

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#455:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#455"`, kept where the row or its body names `#455`:
    456	Both roots' scripts/bootstrap.py: `main` escapes what stdout cannot encode and spells each value it prints, so `name: "a\\ud83db"` or `日本` under `PYTHONIOENCODING=ascii` no longer ends `make bootstrap` in a traceback (#391)	fix/bootstrap-print-name-escaped	MERGED
    473	docs/release-notes/v1.0.0.md: the v1.0.0 release notes, a draft for Brett to amend before he tags, saying what 1.0 contains and promises, how to pin it by commit, what changed on the way grouped by the five tracking areas, and every known limit as the input that triggers it and what the person sees (#472)	docs/v1.0.0-release-notes	MERGED
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `455` 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