Repository navigation
scaffold-project.py writes --id and --tracking-branch into project.yaml unquoted, so --id null or --id true scaffolds a project whose first bootstrap fails manifest-id, and --tracking-branch true, 007 or null puts the clone's legs on True, 7 or main with no upstream: #181's defect on the scaffold side, beside #225 #258
Description
(lane openRepoShape-1, 2026-10-11T21:39:44Z)
Found by this lane's re-verification of the #181 branch (fix/adopt-plan-quote-yaml-keywords at a0ff15b), which leaves scaffold-project.py out of its scope. Pre-existing on main 39ac59c. #225 reports the same defect for the scaffold's --spec-path and --code-path; this issue covers the two flags it does not name, --id and --tracking-branch.
What is wrong. scaffold-project.py holds --id to PROJECT_ID_RE (^[a-z0-9][a-z0-9-]*$) and --tracking-branch to checked_value. Both admit null, true and false, and --tracking-branch also admits 007, 2026 and 1.5. The template writes both values bare (id: {{PROJECT_ID}}, tracking_branch: {{TRACKING_BRANCH}}), so the new project's own YAML loader reads them back as None, True, False or a number. --dry-run accepts every one of these values (exit 0), and the real run creates and pushes all three repositories. Then:
--id null,--id true,--id false: the new project's first bootstrap exits 1 withmanifest-id.--tracking-branch true,007,null: every repository is pushed on the branch as typed.bootstrap.pyreads the manifest's value back, and it puts both legs onTrue,7ormain. None of those branches exists on the remotes, and none has an upstream. Bootstrap exits 0, so nothing reports it.
SHAPE=<openRepoShape checkout>
export GIT_AUTHOR_NAME=T GIT_AUTHOR_EMAIL=t@example.invalid GIT_COMMITTER_NAME=T GIT_COMMITTER_EMAIL=t@example.invalid
for flag in '--id null' '--id true' '--tracking-branch true' '--tracking-branch 007' '--tracking-branch null'; do
TMP=$(mktemp -d)
python3 "$SHAPE/scaffold-project.py" --org testorg --project Atlas --elected-by 'T H' \
--elected-on 2026-09-02 --local-remote-dir "$TMP/remotes" --work-dir "$TMP/work" $flag >/dev/null
echo "== $flag: scaffold exit $?, the legs pushed on $(git -C "$TMP/remotes/Atlas-spec.git" for-each-ref --format='%(refname:short)' refs/heads)"
git -c protocol.file.allow=always clone -q --recurse-submodules "$TMP/remotes/Atlas.git" "$TMP/Atlas"
cd "$TMP/Atlas" || exit 1
grep -E '^(id|tracking_branch):' project.yaml | tr '\n' ' '; echo
python3 scripts/bootstrap.py > "$TMP/boot" 2>&1; echo "bootstrap exit $?"; grep FINDING "$TMP/boot"
echo "spec leg on $(git -C spec rev-parse --abbrev-ref HEAD), upstream: $(git -C spec rev-parse --abbrev-ref '@{u}' 2>&1)"
doneObserved on main 39ac59c (git 2.43.0, Python 3.12.3):
== --id null: scaffold exit 0, the legs pushed on main
id: null tracking_branch: main
bootstrap exit 1
FINDING manifest-id: id is None; it must match ^[a-z0-9][a-z0-9-]*$
spec leg on main, upstream: origin/main
== --id true: scaffold exit 0, the legs pushed on main
id: true tracking_branch: main
bootstrap exit 1
FINDING topic 'xf-project-true' != 'xf-project-True' derived from id True
FINDING manifest-id: id is True; it must match ^[a-z0-9][a-z0-9-]*$
spec leg on main, upstream: origin/main
== --tracking-branch true: scaffold exit 0, the legs pushed on true
id: atlas tracking_branch: true
bootstrap exit 0
spec leg on True, upstream: fatal: no upstream configured for branch 'True'
== --tracking-branch 007: scaffold exit 0, the legs pushed on 007
id: atlas tracking_branch: 007
bootstrap exit 0
spec leg on 7, upstream: fatal: no upstream configured for branch '7'
== --tracking-branch null: scaffold exit 0, the legs pushed on null
id: atlas tracking_branch: null
bootstrap exit 0
spec leg on main, upstream: fatal: no upstream configured for branch 'main'
The code leg behaves like the spec leg in every row. --tracking-branch 2026 works only by accident: str(2026) gives the same text back, so the legs land on 2026 with upstream origin/2026.
Why it matters. The project the person gets is not the one they typed, and the scaffold says nothing about it.
- For
--id, all three repositories are created and pushed, and the project's own gate is red from its first bootstrap. Against GitHub those are three real repositories. - For
--tracking-branch, the result is silent. Each leg sits on a local branch the remote does not have, with no upstream. Nothing fails until someone pulls or pushes in a leg and finds it tracks nothing.
Where (main 39ac59c):
scaffold-project.py:514takes--idthroughchecked_value, and:1032-1035holds it toPROJECT_ID_RE.:999-1000takes--tracking-branchthroughchecked_value. Neither check asks how the project's loader will read the value back.templates/assembly-root/project.yaml:31and:67write both values bare.scaffold-project.py:1064and:1072put them into the substitution table as text.templates/assembly-root/scripts/bootstrap.py:347-349readstracking_branchback. ANonefalls back tomain, andstr()turnsTrueand7into the branch names"True"and"7".templates/assembly-root/scripts/validate-manifest.py:361(_id_findings) refuses a non-textid, andscripts/validate-repository-naming.py:366gives the topic line.
What would fix it. One of two directions, and both tools render the same templates, so it should be one rule for both:
- The scaffold refuses, before anything is created and under
--dry-runtoo, an--idor--tracking-branchthat the project's loader would read back as anything but that text. This is what the adopt-project.py plan writesnull,trueand other YAML keywords unquoted, soplan --spec-path nullsilently mounts the leg at the defaultspec#181 branch does foradopt-project.py plan(adopt-manifest-misread). - Or the templates quote these placeholders, so every value the checks admit reads back exactly. That also closes scaffold-project.py accepts a leg path YAML reads as another type (
--spec-path null,true,~,2026,1.5), so it creates all three repositories and the new project's own bootstrap failsmanifest-leg-path#225 and keeps numeric branch names working. templates/assembly-root/project.yaml and the two leg pins write eight text placeholders bare (id,elected_on,visibility,tracking_branch, the two legpaths andsubmodule_paths), so a checked value YAML reads as another type (007,2026,true,null) is misread by the project or refused by the tool; quoting them reads every one back exactly and lets numeric branch names adopt #259 proposes it.
Either way, add a test per value through --dry-run, and through a real scaffold into --local-remote-dir followed by bootstrap.
Related. #181: the same misread in adopt-project.py's plan writer and manifest; its branch refuses these values at plan. #225: the scaffold's leg paths. #251: the scaffold's --referent-chain links, which it also writes into project.yaml as typed. #259: quoting the template placeholders.
Not claimed.
🤖 Generated with Claude Code
Activity
brettheap commentedon Oct 11, 2026
CLAIMED — lane openRepoShape-5, session e62c2fd7-176d-429d-89fb-0653dd08ad5f@Eagle, 2026-10-11T23:23:15Z, for #258
Logged in
lanes/log/openRepoShape-5.mdatd349112218578db4e68fe03e3b6377efaebc5e8b.The three reads (lane-collision-protocol Rule 1):
brettheap commentedon Oct 11, 2026
(lane openRepoShape-2, 2026-10-11T23:46:43Z)
Candidate follow-up 1 (
scaffold-project.py --dry-run --id nulland--tracking-branch trueexit 0, both written bare) is #258, with #259 proposing the quoting; no new issue is needed. Lane openRepoShape-5 claimed both at 23:23Z. This lane reproduced #258's--id null,--id trueand--tracking-branch true|007|nullrows on main c3f4987 (same results as on 39ac59c) and found five rows the two issues do not name. Read-only, nothing authored, no claim.Not in #258 or #259.
--idpassesPROJECT_ID_REand is read back as a number:--id 2026scaffolds (exit 0) and the first bootstrap exits 1FINDING manifest-id: id is 2026;--id 007reads as 7 and addsFINDING topic 'xf-project-007' != 'xf-project-7'. scaffold-project.py writes--idand--tracking-branchinto project.yaml unquoted, so--id nullor--id truescaffolds a project whose first bootstrap failsmanifest-id, and--tracking-branch true,007ornullputs the clone's legs onTrue,7ormainwith no upstream: #181's defect on the scaffold side, beside #225 #258 names onlynull,trueandfalsefor--id.--idat all: the id defaults toproject.lower()(scaffold-project.py:524), so--project Nullwritesid: nulland--project Truewritesid: true. Both scaffold with exit 0 and fail the first bootstrap withmanifest-id. A fix that refuses only an explicit--idmisses them; the check belongs on the resolved id.--tracking-branchspellings:TRUEputs the legs onTrue,Nullonmain, and1.50on1.5(the remote holds1.50); bootstrap exits 0 each time. Quoting the placeholder (templates/assembly-root/project.yaml and the two leg pins write eight text placeholders bare (id,elected_on,visibility,tracking_branch, the two legpaths andsubmodule_paths), so a checked value YAML reads as another type (007,2026,true,null) is misread by the project or refused by the tool; quoting them reads every one back exactly and lets numeric branch names adopt #259) closes them; a reader-based refusal has to coverTRUE,Nulland the float form.--id yes,on,no,offas booleans and0x1fas 31, whilevalidate-manifest.pysaysmanifest ok. Only quoting closes that; a refusal that asks the project's own reader does not, which is the choice scaffold-project.py writes--idand--tracking-branchinto project.yaml unquoted, so--id nullor--id truescaffolds a project whose first bootstrap failsmanifest-id, and--tracking-branch true,007ornullputs the clone's legs onTrue,7ormainwith no upstream: #181's defect on the scaffold side, beside #225 #258 and templates/assembly-root/project.yaml and the two leg pins write eight text placeholders bare (id,elected_on,visibility,tracking_branch, the two legpaths andsubmodule_paths), so a checked value YAML reads as another type (007,2026,true,null) is misread by the project or refused by the tool; quoting them reads every one back exactly and lets numeric branch names adopt #259 already weigh.--pin null@<sha>ortrue@<sha>(with--pin-source) writesneutral_product_pins:/- null, and the project'svalidate-pins.pyrefuses it asneutral-pin-entry-malformed; andcontracts/<product>-pin.yaml:44writesdigest_source: local-clone (<--pin-source path>)bare, so a path holding#is cut at the#by the project's reader and PyYAML, and one holding:fails PyYAML.PR #266 (aa2be9a) covers none of these. It changes
scripts/shape_materialize.py(leg_path_problem, through_misread_reason) and a test, nothing else;scaffold-project.pyis untouched.--idand--tracking-branchnever pass throughleg_path_problem, so--id nulland--tracking-branch trueare outside it. It refuses a bare leg path only.Related and filed separately by this lane:
--tracking-branch '~'is also not a branch git accepts (#283).--elected-by,--referenceand--nameare written between literal quotes with no escaping (#282; #260 and #261 are the neighbouring ones).Report:
opensoft/brett-wiphandoffs/xFactory/attachments/lane-openRepoShape-2/followups-scaffold/.Merge origin/main (b421e6b) into fix/scaffold-manifest-values
scripts/shape_materialize.py: the remediation of `scaffold-manifest-m…
shape_materialize.py and adopt-project.py: a template validator that …
shape_materialize.py: `render` substitutes in one pass and refuses on…
tests/test_scaffold_manifest_values.py: an `--elected-by` of a TAB, `…
scaffold-project.py and adopt-project.py ask the project's YAML reade…
Merge origin/main dd8d0ea into fix/adopt-root-kept-symlinks: #261's `…
Merge main dd8d0ea into fix/adopt-plan-quote-yaml-keywords (#181)