Skip to content

scaffold-project.py --pin-source <path>: the path is written as it stands into the new root's contracts/<product>-pin.yaml (digest_source: local-clone (<path>), a bare placeholder) and is asked nothing first, so a clone at a path holding a line break scaffolds, exit 0, a pin file with a line of the path's choosing, and one at a path that is not UTF-8 creates all three repositories and then dies in write_lf with a UnicodeEncodeError traceback, exit 1 #450

Closed

Description

@brettheap

Lane: openreposhape-5 (openRepoShape-5)

(lane openRepoShape-5, 2026-10-12T18:54:39Z)

Found by lane 5's review of fix/adopt-pin-writes-pin-file at 7a94b6b (the fix for #273 and #339), its N2. That PR fixes the adopt half: adopt-project.py plan now refuses such a --pin-source before it reads a digest, as adopt-unwritable-value. The scaffold half is pre-existing and is left to this issue. Reproduced on origin/main 45e01f4 and on that branch's fix-round head 74ce4d9 (same scaffold code). Not claimed.

What is wrong. declared_pin_values (scaffold-project.py on main; scripts/shape_materialize.py once that PR lands) records each pin's digest_source as local-clone ({args.pin_source}), the path as typed. The template writes it bare: digest_source: {{PIN_DIGEST_SOURCE}} in templates/assembly-root/contracts/neutral-product-pin.yaml, and render quotes only the placeholders a template puts between double quotes. #351 (d82a83e) asks unwritable_reason of --elected-by, --reference and --name in _identity, before anything exists, but not of --pin-source. So:

SHAPE=<openRepoShape checkout>
# Every repository is local; keep the real `gh` out of reach anyway.
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)
git init -q -b main "$D/openGlass" && cd "$D/openGlass" || exit 1
printf '# openGlass\n' > README.md && git add -A && git commit -qm one && A=$(git rev-parse HEAD)
cd "$D" || exit 1
cp -a openGlass "$D/gl
ass"                                           # a clone at a path holding a line break
python3 "$SHAPE/scaffold-project.py" --org testorg --project MedxGlass --elected-by 'T H' \
    --elected-on 2026-09-02 --pin "openGlass@$A" --pin-source "$D/gl
ass" --local-remote-dir "$D/r1" --work-dir "$D/w1" > /dev/null; echo "exit $?"
git -C "$D/r1/MedxGlass.git" show main:contracts/openglass-pin.yaml | grep -A1 '^digest_source:'
cp -a openGlass "$D/gl$(printf '\377')ass"     # a clone at a path that is not UTF-8
python3 "$SHAPE/scaffold-project.py" --org testorg --project MedxGlass --elected-by 'T H' \
    --elected-on 2026-09-02 --pin "openGlass@$A" --pin-source "$D/gl$(printf '\377')ass" \
    --local-remote-dir "$D/r2" --work-dir "$D/w2" 2>&1 >/dev/null | tail -n 1; ls "$D/r2"

Observed on 45e01f4 and on 74ce4d9 (by an equivalent driver), Python 3.12.3, Linux:

--pin-source scaffold-project.py (a real run)
a clone at <dir>/gl LF ass exit 0, three repositories; contracts/openglass-pin.yaml line digest_source: local-clone (<dir>/gl, then the line ass)
a clone at <dir>/gl\xffass exit 1, UnicodeEncodeError: 'utf-8' codec can't encode character '\udcff' ... surrogates not allowed from write_lf; MedxGlass.git, MedxGlass-spec.git and MedxGlass-code.git all exist, the root with no main

--dry-run reaches neither: it computes the digest (the clone is real, so it answers) and prints the plan, exit 0, the path raw in the plan's pin line.

What it should do. Refuse such a path before anything exists, --dry-run alike, in the words the two tools already share:

Quoting the template's digest_source: instead (#259's open question) would make the file hold a line break, but not a byte UTF-8 cannot encode, and is Brett's decision; the refusal is the lane's direction meanwhile.

How to test. In tests/test_scaffold_free_text_values.py (#351's file) or tests/test_pin_source_unreadable.py (#339's, which runs each case through --dry-run, the real run and adopt-project.py plan), add a --pin-source holding LF, CR, ESC, U+200D and (POSIX only) a byte that is not UTF-8, asserting exit 2, the refusal naming the value repr'd, --dry-run and the real run refusing alike, and no remotes/work directory made. The path need not exist: the refusal comes before any digest is read. Reverting the fix fails it.

Not in this issue.

Related: #273, #339, #351, #259.

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