Repository navigation
Description
Lane: openreposhape-5 (openRepoShape-5)
Found by lane 5's writer of #258 and #261 (fix/scaffold-manifest-values at ca21b57), checking which other writers of the constructs that fix guards it does not reach. The --name row is lane 5's writer of #260 (fix/render-single-pass at 552763d). Pre-existing: the runs below are on main ede15ce (git 2.43.0, Python 3.12.3, Linux), into local bare remotes (--local-remote-dir). gh was stubbed to fail and family.py init never called it.
What is wrong. scripts/family.py init renders templates/family-root/family.yaml from the table _template_values builds (scripts/family.py:599-622). The holder it creates carries its own YAML reader and its own scripts/validate-family.py, and init asks neither of them anything:
--idgoes throughchecked_valueandPROJECT_ID_RE(:536-537),--organd--tracking-branchthroughchecked_value,--created-bythrough "not empty" (args.created_by or ...,:554), and--created-onand--namethrough nothing (:616,:609). Only--familyis classified against the naming policy (:542).- The template writes
id,org,created_on,visibilityandtracking_branchbare (templates/family-root/family.yaml:33,:35,:41,:46,:51) andnameandcreated_bydouble-quoted (:34,:40).
So a value the holder's validator refuses is written and pushed anyway, and a value YAML reads as another type is misread. In every row below --dry-run exits 0 and so does the real run.
| value | what init checks |
what the holder's own validate-family.py says |
|---|---|---|
--created-by ' ' |
not empty | family-created-by: blank after strip() |
--created-on 20261011, 2026-W41-6, tomorrow |
nothing | family-created-on: DATE_RE is ^\d{4}-\d{2}-\d{2}$ (and 20261011, written bare, reads back as a number) |
--id null, true, false |
PROJECT_ID_RE, which admits all three |
family-id: written bare, read back as None, True, False |
--org null |
checked_value |
family-org: read back as None |
--name 'Contoso Group' |
nothing (--name is documented as "display name", :1706) |
naming-unclassified: the validator classifies name as a family holder, one CamelCase token (ContosoGroup passes) |
--tracking-branch true, 007, null |
checked_value |
no finding at all; read back as True, 7, None |
The last row is silent. The holder is pushed on the branch as typed (true, 007, null), validate-family.py has no rule about tracking_branch, and the next tool to read the field gets the misread value: update-shape.py apply --branch on that holder prints a gh pr create whose --base is True, 7 or main (tracking_branch_of, update-shape.py:1607, is str(... or "main")). --tracking-branch 2026 works only by accident, as #258 says of the scaffold: str(2026) gives the same text back.
SHAPE=<openRepoShape checkout> # a clean checkout of main
export GIT_AUTHOR_NAME=T GIT_AUTHOR_EMAIL=t@example.invalid GIT_COMMITTER_NAME=T GIT_COMMITTER_EMAIL=t@example.invalid
UP=$(mktemp -d)/openRepoShape # an upstream one commit ahead of the holders' shape, for update-shape.py below
git clone -q "$SHAPE" "$UP" && printf '\n# a fix\n' >> "$UP/templates/family-root/scripts/validate-family.py"
git -C "$UP" -c user.name=T -c user.email=t@example.invalid commit -qam 'Fix the family validator'
# the first two rows are controls: a plain branch and a one-token name
for flag in "--tracking-branch=main" "--name=ContosoGroup" \
"--created-by= " "--created-on=20261011" "--created-on=2026-W41-6" "--created-on=tomorrow" \
"--id=null" "--id=true" "--id=false" "--org=null" "--name=Contoso Group" \
"--tracking-branch=true" "--tracking-branch=007" "--tracking-branch=null" "--tracking-branch=2026"; do
TMP=$(mktemp -d)
init() { python3 "$SHAPE/scripts/family.py" init --org testorg --family Contoso --created-by 'T H' --created-on 2026-09-02 \
--local-remote-dir "$TMP/remotes" --work-dir "$TMP/work" "$flag" "$@" > /dev/null 2>&1; }
init --dry-run; printf "== init '%s': --dry-run exit %s, " "$flag" $?
init; printf 'real run exit %s, the holder pushed on %s\n' $? "$(git -C "$TMP/remotes/Contoso.git" for-each-ref --format='%(refname:short)' refs/heads)"
(cd "$TMP/work/Contoso" && python3 scripts/validate-family.py > "$TMP/val" 2>&1; echo " validate-family.py exit $?: $(grep -m1 FINDING "$TMP/val")")
python3 - "$SHAPE" "$TMP/work/Contoso/family.yaml" <<'EOF'
import sys; sys.path.insert(0, sys.argv[1] + "/scripts")
from repo_shape import load_yaml
m = load_yaml(sys.argv[2])
print(" family.yaml reads back:", {k: m[k] for k in ("id", "org", "name", "created_by", "created_on", "tracking_branch")})
EOF
case "$flag" in --tracking-branch=*)
python3 "$SHAPE/update-shape.py" apply --root "$TMP/work/Contoso" --yes --upstream "$UP" --at "$(git -C "$UP" rev-parse HEAD)" \
--branch resync | grep -o -E -- '--repo [^ ]+ --base [^ ]+ --head [^ ]+' | sed 's/^/ update-shape.py apply --branch prints: gh pr create /';;
esac
doneObserved on main ede15ce:
== init '--tracking-branch=main': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 0:
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': 'main'}
update-shape.py apply --branch prints: gh pr create --repo testorg/Contoso --base main --head resync
== init '--name=ContosoGroup': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 0:
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'ContosoGroup', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': 'main'}
== init '--created-by= ': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 1: FINDING family-created-by: created_by is empty. Creating a family is a human's act and the manifest records whose.
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'Contoso', 'created_by': ' ', 'created_on': '2026-09-02', 'tracking_branch': 'main'}
== init '--created-on=20261011': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 1: FINDING family-created-on: created_on is 20261011, expected an ISO date YYYY-MM-DD
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': 20261011, 'tracking_branch': 'main'}
== init '--created-on=2026-W41-6': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 1: FINDING family-created-on: created_on is '2026-W41-6', expected an ISO date YYYY-MM-DD
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': '2026-W41-6', 'tracking_branch': 'main'}
== init '--created-on=tomorrow': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 1: FINDING family-created-on: created_on is 'tomorrow', expected an ISO date YYYY-MM-DD
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': 'tomorrow', 'tracking_branch': 'main'}
== init '--id=null': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 1: FINDING family-id: id is None; it must match ^[a-z0-9][a-z0-9-]*$
family.yaml reads back: {'id': None, 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': 'main'}
== init '--id=true': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 1: FINDING family-id: id is True; it must match ^[a-z0-9][a-z0-9-]*$
family.yaml reads back: {'id': True, 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': 'main'}
== init '--id=false': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 1: FINDING family-id: id is False; it must match ^[a-z0-9][a-z0-9-]*$
family.yaml reads back: {'id': False, 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': 'main'}
== init '--org=null': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 1: FINDING family-org: org is None
family.yaml reads back: {'id': 'contoso', 'org': None, 'name': 'Contoso', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': 'main'}
== init '--name=Contoso Group': --dry-run exit 0, real run exit 0, the holder pushed on main
validate-family.py exit 1: FINDING naming-unclassified: 'Contoso Group' matches no family in the naming policy. A family holder is one CamelCase token with no hyphen, underscore, dot or space.
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'Contoso Group', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': 'main'}
== init '--tracking-branch=true': --dry-run exit 0, real run exit 0, the holder pushed on true
validate-family.py exit 0:
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': True}
update-shape.py apply --branch prints: gh pr create --repo testorg/Contoso --base True --head resync
== init '--tracking-branch=007': --dry-run exit 0, real run exit 0, the holder pushed on 007
validate-family.py exit 0:
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': 7}
update-shape.py apply --branch prints: gh pr create --repo testorg/Contoso --base 7 --head resync
== init '--tracking-branch=null': --dry-run exit 0, real run exit 0, the holder pushed on null
validate-family.py exit 0:
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': None}
update-shape.py apply --branch prints: gh pr create --repo testorg/Contoso --base main --head resync
== init '--tracking-branch=2026': --dry-run exit 0, real run exit 0, the holder pushed on 2026
validate-family.py exit 0:
family.yaml reads back: {'id': 'contoso', 'org': 'testorg', 'name': 'Contoso', 'created_by': 'T H', 'created_on': '2026-09-02', 'tracking_branch': 2026}
update-shape.py apply --branch prints: gh pr create --repo testorg/Contoso --base 2026 --head resync
Run on the --created-on 20261011 and the --name 'Contoso Group' holders, shape-doctor.py --root <holder> ends INVALID (family) (exit 1) with the validator's finding as its family row.
Why it matters.
- Each row of the first five ends
initexit 0 over a holder that exists and is pushed, whose gate is red from its first commit.validate-family.pyis what the holder'smake validateruns first (templates/family-root/Makefile:52) and what itsvalidateworkflow runs (.github/workflows/validate.yml:191, and:202with--no-members). initcannot be run again for that holder: with the commit pushed, the same command with a corrected value endsREFUSED family-remote-exists: <remote> already exists and has commitsandThere is no --force., with or without--reuse-empty-repo(run). The way back is a hand edit offamily.yaml, which AGENTS.md:211-212 tells an agent never to make to turn a row green.- Against GitHub,
_create_holder_remoterunsgh repo create(:737, called at:848) before the manifest is rendered (_create_holder_worktree,:744), so each of these rows would first create a real repository. Read from code; the GitHub path was not run. - The
--tracking-branchrow is silent. Nothing fails: the holder sits ontruewhile its manifest saysTrue, and the pull request base a tool prints is a branch the holder does not have.
Expected. The same principle #261's fix applies to scaffold-project.py and adopt-project.py: refuse, before anything is created and under --dry-run too, what the holder's own programs refuse, by asking those programs and not by copying their rules. Concretely, load the validator the template ships (templates/family-root/scripts/validate-family.py) from that file and run it over the family.yaml the run would write, and read each rendered line with repo_shape.parse_yaml, as #261's manifest_validator, manifest_invalid and manifest_misreads do for the assembly root.
- Where
family.pybuilds its table:_template_values(scripts/family.py:599-622), called fromcmd_initat:836. That is before_print_init_planand the--dry-runreturn (:840-842) and before_create_holder_remote(:848), so the question belongs there, over the table and the rendered template. - Those functions are in
scripts/shape_materialize.pyand take atemplate_dir, but read the assembly root'sproject.yaml, itsTEMPLATEDlist andvalidate-manifest.py's_findings. The holder needs the manifest's name, its own templated list (FAMILY_TEMPLATED) and the validator's entry point (_check_envelope(manifest, policy, report), thenReport.findings) passed in. Read from code, not run. - The YAML-type misreads (
id,org,created_on,tracking_branch) follow whatever 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 decides. If the templates quote the placeholders, those values read back exactly and--id nullbecomes the textnull, whichPROJECT_ID_REaccepts; if the tools refuse them,initrefuses as adopt-project.py and scaffold-project.py accept values the new project's own validate-manifest.py refuses (a blank--elected-byor--reference,--elected-on 2026-W41-6or20261011on Python 3.11+,--org test+org, a plan'svisibility: secret), soadoption verifiedor a scaffold exit 0 is followed by a failed first bootstrap; andplanadmits a 251-255-character--projectthatcheckrefuses #261's fix does. Either way the validator question stays needed for what the validator refuses (--created-by ' ',--created-on 20261011,--name 'Contoso Group'). Note 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 lists four bare lines offamily.yaml(:33,:41,:46,:51) and notorg(:35), which is bare too. tracking_branchis the one fieldvalidate-family.pynever judges, so only the misread rule or the quoting protects it.- Add a test per row through
--dry-run, and through a realinitinto--local-remote-dirfollowed by the holder'svalidate-family.py.
Related. #258: the scaffold's --id and --tracking-branch, the same misread. #259: quoting the template placeholders, which lists family.yaml. #261: the values the validator refuses, in the scaffold and adopt; its fix (fix/scaffold-manifest-values at ca21b57) changes neither scripts/family.py nor templates/family-root/. #260: render's placeholder expansion; its writer found the --name row.
Not claimed.
- No run against GitHub: the
gh repo createordering is read from code. - No run on macOS or Windows.
--visibilityis held by argparse choices and was not a row.--created-by 'a"b'is escaped by the renderer and the validator accepts it, and--org test+orgpassesvalidate-family.py; neither is claimed.- A branch name git itself refuses (
a..b,x.lock) is scaffold-project.py holds--tracking-branchonly tochecked_value's alphabet, so a name git refuses as a branch (~,a..b,x.lock, a trailing/) passes--dry-runand, against GitHub, dies at the firstgit init -baftergh repo createhas made all three repositories #283's for the scaffold and was not run forinit. family.py add,bumpandremovewere not looked at here.- No fix was written, and whether to quote or refuse is 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's to decide.
🤖 Generated with Claude Code
Activity
brettheap commentedon Oct 12, 2026
CLAIMED — lane openRepoShape-5, session e62c2fd7-176d-429d-89fb-0653dd08ad5f@Eagle, 2026-10-12T11:02:39Z, for #307
Logged in
lanes/log/openRepoShape-5.mdat8e823c25cf1ac33a815b0ffaacfefaf383206a19.The three reads (lane-collision-protocol Rule 1):
shape_materialize.py: `render` substitutes in one pass and refuses on…
scripts/family.py, tests: a validator that exits, or whose `Report` h…
scaffold-project.py and adopt-project.py ask the project's YAML reade…
Merge origin/main 7a8b981 into fix/adopt-root-kept-symlinks: #189's `…
templates: `project.yaml`, the leg pins and `family.yaml` quote their…
family.py init: the creator is asked whether a file can hold it which…
scripts/family.py and shape_materialize.py: `family.py init` asks a t…