Repository navigation
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
Description
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:
- a path holding a LINE BREAK scaffolds, exit 0, and
contracts/openglass-pin.yamlholdsdigest_source: local-clone (<dir>/gland, on the next line,ass): a line of the file that the path chose. A#in a path cuts the value short the same way (the reader takes it for a comment), which the adopt-project.py--pin <name>is accepted byplan,checkandexecute("adoption verified"), but writes only the name intoneutral_product_pins:and nocontracts/<name>-pin.yaml, so the adopted root's ownvalidate-pins.py(and itsvalidate.yml) refuses withneutral-pin-missingfrom the first run #273 PR's body lists as known; - a path holding a byte that is NOT UTF-8 (POSIX;
sys.argvkeeps it as a lone surrogate) creates all three repositories and pushes both legs, thenwrite_lfraisesUnicodeEncodeErroron the pin file, exit 1, and the corrected re-run isscaffold-remote-exists.
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:
- the scaffold asks, of the
--pin-sourceit would record, the questionadopt-project.py planasks since the adopt-project.py--pin <name>is accepted byplan,checkandexecute("adoption verified"), but writes only the name intoneutral_product_pins:and nocontracts/<name>-pin.yaml, so the adopted root's ownvalidate-pins.py(and itsvalidate.yml) refuses withneutral-pin-missingfrom the first run #273 PR (_refuse_an_unrecordable_pin_source:one_printable_line, withunwritable_reason's words where they name it), asscaffold-unwritable-valuebeside scaffold-project.py:--elected-by,--reference,--nameand the elector's fallback are refused before anything exists when no file can hold them, so a byte that is not UTF-8 no longer creates repositories before a traceback (#282) #351's_refuse_unwritable_values, only when a--pinwill record it; - better, ONE function for both: move that check into
scripts/shape_materialize.pybesidedeclared_pin_values, which both tools call, so the two refuse the same paths with one sentence and two codes.
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.
adopt-project.py plan: fixed in the adopt-project.py--pin <name>is accepted byplan,checkandexecute("adoption verified"), but writes only the name intoneutral_product_pins:and nocontracts/<name>-pin.yaml, so the adopted root's ownvalidate-pins.py(and itsvalidate.yml) refuses withneutral-pin-missingfrom the first run #273 PR (N2 of its review).--pin-sourcethat cannot answer (scaffold-project.py:pin_digest_from_sourcecatches onlyRefusal, so a--pin-sourcethat does not exist, is a file, or sits under a parent this user cannot enter ends--dry-runand the real run alike in aFileNotFoundError,NotADirectoryErrororPermissionErrortraceback (exit 1), where a directory that is not a clone is thepin-source-unreadablerefusal (exit 2) with its remediation #339) and its printing (that PR's S1).- A path holding
#(the bare placeholder's comment rule): listed as known in the adopt-project.py--pin <name>is accepted byplan,checkandexecute("adoption verified"), but writes only the name intoneutral_product_pins:and nocontracts/<name>-pin.yaml, so the adopted root's ownvalidate-pins.py(and itsvalidate.yml) refuses withneutral-pin-missingfrom the first run #273 PR, and fixed by the same refusal only if it also refuses#, or by quoting the placeholder.
Activity
adopt-project.py: `plan --pin` takes `product@<40 hex>` and `execute`…
tests: the two `not-utf-8` clone rows skip where the filesystem refus…
adopt-project.py: `plan` records `local-clone (<path>)` in the plan a…
Merge origin/main 55faad2 (#591) into fix/540-pin-source-unread, so t…
adopt-project.py: `plan` refuses a `--pin-source` it cannot record as…