Repository navigation
shape-doctor.py: the legs row's NEXT (and the members row's) runs <root>/scripts/bootstrap.py without asking whether the root has it, so a root that lost it pastes python3: can't open file, exit 2, and for an empty leg mount the legs NEXT would not cure the row even with the file there (bootstrap.py only prints Run git submodule update --init spec) #276
Description
Lane: openreposhape-4 (openRepoShape-4)
Found by this lane's adversarial review of the #238 fix branch (fix/doctor-template-validator-next, head 59724c9; its out-of-scope item O1, which the reviewer ruled a follow-up), and re-verified on main eb6df75 (d89fffa plus #220; #220's only change to shape-doctor.py is check_manifest_kinds at :963-1140, so both rows named below are byte-identical to d89fffa and sit 137 lines lower, and every line cited was read on eb6df75). The second half of the title was found while reproducing the first, and is also verified on eb6df75. It is pre-existing. Not claimed.
What is wrong. Two rows print python3 <root>/scripts/bootstrap.py --root <root> as their NEXT without checking that the file is there: check_legs (shape-doctor.py:1351, the NEXT at :1397-1402) and members_verdict (:2915, the NEXT at :2954-2959, a family holder). scripts/bootstrap.py is one of the shape's copied files, so a root that lost it is exactly what the shape currency row reports as copy-missing, and the same run has two red rows, one of which hands over a remedy that cannot run:
# $U: a clean checkout at eb6df75. gh is a PATH shim that logs argv and exits 97. Every repository is a bare repository under $R/remotes; no network.
python3 $U/scaffold-project.py --org testorg --project Atlas --elected-by 'Test Human' --elected-on 2026-09-02 --local-remote-dir $R/remotes --work-dir $R/work
git -c protocol.file.allow=always clone -q --recurse-submodules $R/remotes/Atlas.git $C
python3 $U/shape-doctor.py --root $C # COMPLIANT (exit 0)
git -C $C rm -q scripts/bootstrap.py ; git -C $C submodule deinit -q -f spec
python3 $U/shape-doctor.py --root $C
# exit 1 DRIFTED (1 copy-missing, 1 leg(s) not at the pin)
# FINDING shape currency 10 unchanged / 0 upstream-changed / ... : scripts/bootstrap.py
# FINDING legs spec: the gitlink is there and the directory is empty -- an unbootstrapped clone
# NEXT python3 $C/scripts/bootstrap.py --root $C # what `make bootstrap` runs: it puts every leg on its tracking branch AT the pinned commit
cd / && sh -c "$NEXT" # the legs NEXT, as printed
# python3: can't open file '$C/scripts/bootstrap.py': [Errno 2] No such file or directory
# exit 2A family holder does the same: family.py init and add --member testorg/Atlas, then git rm scripts/bootstrap.py and git submodule deinit -f members/Atlas: the members row's NEXT pasted is python3: can't open file '$H/scripts/bootstrap.py', exit 2 ($H is the holder).
The second half: the file's presence does not make the legs NEXT work for the row's first case. With the root KEEPING scripts/bootstrap.py and only git submodule deinit -f spec (an empty mount):
python3 $U/shape-doctor.py --root $C2 # exit 1, DRIFTED (1 leg(s) not at the pin); FINDING legs spec: the gitlink is there and the directory is empty -- an unbootstrapped clone
cd / && sh -c "$NEXT" # the legs NEXT
# bootstrap: Atlas ($C2)
# (a) legs on tracking branches, pins untouched
# [spec] spec: NOT INITIALIZED. Run `git submodule update --init spec` — the validators below will refuse until you do.
# [code] code: on main at 3b14932e37c1 (branch tip == pin)
# exit 1
python3 $U/shape-doctor.py --root $C2 # the same FINDING legs, the same verdict
git -c protocol.file.allow=always submodule update --init --recursive # run in $C2
python3 $U/shape-doctor.py --root $C2 # ok legs 2 leg(s) mounted and checked out at their pins; COMPLIANT (exit 0)bootstrap.py never initialises a mount: it says so and stops (templates/assembly-root/scripts/bootstrap.py:109-112; Makefile's bootstrap target is only $(PYTHON) scripts/bootstrap.py). The row's own first problem text is "an unbootstrapped clone", and its NEXT ends "it puts every leg on its tracking branch AT the pinned commit", which is true of a leg that is already mounted. A holder's bootstrap does fetch the members: with the script present, the members row's NEXT exited 0 and the row went ok, so the second half is the legs row's alone. The other legs problems (HEAD unreadable, no 40-hex pin, checked out at another commit) were not probed.
Why. Both rows build the line from the root's path and never test is_file(). validator_row does (shape-doctor.py:862-872: it falls back to the standard's template copy when the root has none, and says "neither ... is readable, so the question cannot be asked" when that is missing too), so the one row family that ran a root's copy already knows the rule; these two do not. AGENTS.md "Checking a repository's compliance" 3 tells an agent "A FINDING names its fix, and you run THAT ... Run the one the row names. Do not improvise a repair" (AGENTS.md:208-214), so an agent holding a row whose command cannot run has no sanctioned move.
What it should do. The NEXT a row prints must be one that exists and that cures the row it sits on. The decisions are the author's:
- When
<root>/scripts/bootstrap.pyis absent. Options: (a) fall back to the standard's template copy, asvalidator_rowdoes. The template importsrepo_shapefrom beside itself (templates/assembly-root/scripts/bootstrap.py:56-57,templates/family-root/scripts/bootstrap.py:55-56), so it needs the environment shape-doctor.pyvalidator_rowfalls back to the standard's TEMPLATE validator when a root carries no copy of its own, and prints that path as the NEXT command without thePYTHONPATHit runs it under, so pasting themanifest,pinsorfamilyrow's NEXT dies withModuleNotFoundError: No module named 'repo_shape'and exit 1, the code a validator uses for a finding #238's fix prints: run bare from$U/templates/assembly-root/scripts/bootstrap.py --root $Cit diesModuleNotFoundError: No module named 'repo_shape', exit 1, and withPYTHONPATH=$U/scriptsit runs (run against the first fixture above it reaches the sameNOT INITIALIZEDline).validator_forandvalidator_command(which build that line, withvalidator_scripts) exist only onorigin/fix/doctor-template-validator-next, head 88c81e0, not on main and with no PR yet (it waits for shape-doctor.py: a project.yaml or family.yaml that is there and does not load is INVALID (manifest) or INVALID (family), exit 1, quoting load_yaml's own refusal and naming the project's own validator, instead of NOT A SHAPE ROOT, exit 2, with an adopt-project.py plan NEXT and a Triad row calling an assembly root a single repository (#205) #232), so this option waits for shape-doctor.pyvalidator_rowfalls back to the standard's TEMPLATE validator when a root carries no copy of its own, and prints that path as the NEXT command without thePYTHONPATHit runs it under, so pasting themanifest,pinsorfamilyrow's NEXT dies withModuleNotFoundError: No module named 'repo_shape'and exit 1, the code a validator uses for a finding #238; (b) printgit -C <root> submodule update --init --recursive, which needs no script (for thelegsrow's empty-mount case it is the whole cure, shown above); (c) print no new command and defer to theshape currencyrow'supdate-shape.py checkNEXT, which already names the missing copy. - Whether the
legsNEXT depends on which problem fired. One command was written for five problems (:1380-1394): an empty mount wants a submodule update, a leg checked out at another commit wants the checkout or the pin moved, and a leg that is not a submodule at all has no cure here (only the empty mount was probed). Whatever the answer, the NEXT of an empty mount must end with the row green. - The
membersrow: the same existence check, with the holder's template (templates/family-root/scripts/bootstrap.py); its script does cure an unpopulated member. - The
# <comment>tail on both lines is the subject of its own issue (a pasted comment is not a comment in a stock interactive zsh); whichever way that goes, the text of these two tails changes with this.
How to test. In tests/test_shape_doctor.py, with the project fixture (an assembly root) and the holder fixture (:392): (a) git rm scripts/bootstrap.py plus git submodule deinit -f spec (the repository's FILE_PROTOCOL options for the later update), take the legs row's next from --json, run it exactly as printed through sh -c from another working directory, and assert it neither exits 2 nor prints can't open file; (b) after running it, the doctor's legs row is ok; (c) the same pair for a root that KEEPS scripts/bootstrap.py with an empty mount (this one fails today at (b)); (d) the members row on a holder without scripts/bootstrap.py. Under decision 1(a), also the pasted template line from a working directory with no PYTHONPATH.
Related: #238 (the validator_row NEXT environment; this is the other row family the same principle covers), PR #232 (its base), the shape currency row (copy-missing) and update-shape.py check.
Activity
brettheap commentedon Oct 12, 2026
CLAIMED — lane openRepoShape-4, session 3431c370-df80-4c11-a041-41bce42d8b38@Eagle, 2026-10-12T11:06:44Z, for #276
Logged in
lanes/log/openRepoShape-4.mdatb7f59aef593970680c18b8d2e0adb8fc5fd907ba.The three reads (lane-collision-protocol Rule 1):
shape-doctor.py: fix round on the review of c30bfb2, so a member the …
Merge origin/main (doctor A, #354) into fix/doctor-validator-cannot-run
shape-doctor.py: the `legs` and `members` lockstep line is withheld w…
Merge origin/main (1f90a0f: #304 via #371, #276 via #354, #323 via #380…
Merge origin/main 92e29d9 into fix/adopt-root-kept-symlinks: #190's r…
Merge #357's head e44d860 (fix/doctor-validator-cannot-run) into fix/…
shape-doctor.py: doctor B's crash line and `repo_shape.py` restore li…
shape-doctor.py: a NEXT is only the command, and the sentence that en…