Repository navigation
Description
Lane: openreposhape-3 (openRepoShape-3)
The parent issue for one area of openRepoShape: keeping a project current: re-syncing its copied shape files (update-shape.py) and moving a leg's pin (bump-leg.py). Brett Heap ruled on 2026-10-12 that the open issues are grouped under one parent per area, with a release milestone. Every open issue in this area is a sub-issue here.
v1.0.0. The first release is "core flows, all OSes": every procedure in AGENTS.md works end to end for ordinary repositories on Linux, macOS and Windows. The tag waits for every in-scope issue to close and for a full test run to pass on all three OSes on the release commit. In this area, the sub-issues in the v1.0.0 milestone are:
- update-shape.py:
check's andapply's NEXT lines print the root path unquoted wherelanding_failed's and the rollback hand-fix lines quote it withshlex, so a root under a path with a space pastes as a different command, and none of them quotes for cmd.exe or PowerShell #325 (P2):checkandapplyNEXT lines print the root unquoted: a root underMy ProjectsorOneDrive - Orgpastes as another command. make bootstrap,bump-leg.pyandupdate-shape.py applyrun the project's ownvalidate-pins.pyin the operator's environment, so for a project that pins a neutral product with no local checkout of it each one callsgh api repos/<org>/<product>/git/trees/<sha>with the operator's token, thoughbump-leg.pysays "a project runs its own gate offline" andupdate-shape.pythat "applyis otherwise offline": the three callers #297 named and left #411 (P2): Only a project that declares descent: bootstrap, bump-leg and apply callgh apiwith the operator token though README says offline. Docs fix.- update-shape.py
apply --branchjudges the pin, the manifest and the copies from the working tree but commits HEAD plus only the paths it wrote, so over an uncommitted in-placeapplyit exits 0 with a commit whose ownvalidate-pins.pyis red, dies on git's "nothing to commit", or says "nothing to do" and makes no branch #436 (P2): After an uncommitted in-placeapply, a laterapply --branchexits 0 with a commit its own validator rejects, or "nothing to do" with no branch.
Known limits. Every other sub-issue is a known limit: only exotic input triggers it (bytes that are not UTF-8, the C locale, control characters, hand-edited YAML, symlink tricks, names that differ only by case). The release notes list them, and they are worked when a release takes them in. A new edge case that review finds goes on this checklist, not into a new issue, unless it is in a release's scope or would hit a real user (Brett Heap, 2026-10-12).
Edge cases found in review of lane openRepoShape-3's pull requests, kept here rather than filed:
- → bump-leg.py
--to <an annotated tag's own id>: the dry run previews it and exits 0, and the validators refuse the real run only after the rewrite #534bump-leg.py --to <an annotated tag's own id>: the dry run previews the tag's id as the new commit and exits 0, while the real run is refused only by its validators after the rewrite (rolled back, exit 2), so the dry run does not say what the real run does. From family.py:bumprefuses a--tothat names an annotated tag, a tree or a blob asmember-to-not-a-commitbefore the checkout, naming the commit a tag names, and its dry run refuses alike, so a release tag's own id no longer exits 0 with a commit pinning the tag over a gitlink at its commit, which the holder'svalidate-family.pyrefused asmember-gitlink-mismatch(#405) #466's "Left out". - → update-shape.py: four messages (a commit line when there is nothing to commit, a source path nothing names, the commit message's hand-edit claim, a bare
gh pr create) #535 A branchlessapplythat moves the pin back to what HEAD already commits still prints a commit line, and pasted, git answers "nothing to commit". No commit comes out red, so it is outside update-shape.pyapply --branchjudges the pin, the manifest and the copies from the working tree but commits HEAD plus only the paths it wrote, so over an uncommitted in-placeapplyit exits 0 with a commit whose ownvalidate-pins.pyis red, dies on git's "nothing to commit", or says "nothing to do" and makes no branch #436. From update-shape.py:apply --branchcompares the pin, the manifest and every pinned copy with HEAD before its first write and refuses a difference asupdate-branch-uncommitted, so an in-placeapplyleft uncommitted no longer ends in a commit its own validate-pins.py refuses, in git's "nothing to commit", or in "nothing to do" with no branch made (#436) #474's "Left out". - → Printed commands are POSIX-only: quote the bare paste lines in bump-leg.py and update-shape.py, and say in the docs that pasted commands are for a POSIX shell #532 bump-leg.py prints three more commands with the root bare, so a root under
My Projects/pastes as another command, the defect update-shape.py:check's andapply's NEXT lines print the root path unquoted wherelanding_failed's and the rollback hand-fix lines quote it withshlex, so a root under a path with a space pastes as a different command, and none of them quotes for cmd.exe or PowerShell #325 names for update-shape.py: theswitch -cremedies ofbump-leg-root-detachedandbump-leg-on-tracking-branch(scripts/bump-leg.py:1028 and :1038) and the closingNEXT git -C <root> push -u origin <branch>(:1514); bump-leg.py:--dry-runruns on the project's tracking branch and ends by naming thegit switch -cthe real run needs, so step 1 of "Advancing a leg" prints theold -> newline on a fresh project instead of being refusedbump-leg-on-tracking-branchbefore it (#477) #484 quotes only its own new dry-run line. (Not asked for; found while checking bump-leg.py:--dry-runruns on the project's tracking branch and ends by naming thegit switch -cthe real run needs, so step 1 of "Advancing a leg" prints theold -> newline on a fresh project instead of being refusedbump-leg-on-tracking-branchbefore it (#477) #484's quoting.) From the review of bump-leg.py:--dry-runruns on the project's tracking branch and ends by naming thegit switch -cthe real run needs, so step 1 of "Advancing a leg" prints theold -> newline on a fresh project instead of being refusedbump-leg-on-tracking-branchbefore it (#477) #484. - → bootstrap.py: a commit on a detached leg, the
not moving an existing branchtext, and what bump-leg.py and AGENTS-shape.md still promise (one re-pin) #533 Since templates/assembly-root/scripts/bootstrap.py: a tracking branch behind the pin is fast-forwarded to it, and a fresh clone's branch past the pin with no commit of its own is moved back to it, sobump-leg.pythenmake bootstrap, andgit clone --recurse-submodulesthenmake bootstrap, leave the leg on its branch AT the pin, no longer detached undernot moving an existing branch(#475) #490 (7348c57),make bootstrapputs a leg back on its tracking branch only when that branch is at or behind the pin, or ahead of it with no commit of its own; a branch with commits of its own past the pin is not moved, and the leg stays detached at the pin. Three texts still promise more: AGENTS.md:355 and README.md:995 ("make bootstrapre-places it on its tracking branch AT the new pin"), and bump-leg.py's unconditional print after every bump (scripts/bump-leg.py:1513). Every project's AGENTS-shape.md and docs/handbook.html carry the same promise. Lane openRepoShape-5's suggested words, from its read of templates/assembly-root/scripts/bootstrap.py: a tracking branch behind the pin is fast-forwarded to it, and a fresh clone's branch past the pin with no commit of its own is moved back to it, sobump-leg.pythenmake bootstrap, andgit clone --recurse-submodulesthenmake bootstrap, leave the leg on its branch AT the pin, no longer detached undernot moving an existing branch(#475) #490: "make bootstrapin the root puts it back on its tracking branch when that branch is at or behind the new commit, or ahead of it with no commit of its own; a branch with commits of its own past the pin is not moved (bootstrap saysnot moving an existing branch), and the leg stays detached at the pin. Say which when you relay the result." Stale the other way: shape-doctor.py:2914 and :2989 and tests/test_doctor_legs_members_next.py:10 still say bootstrap never moves an existing branch whose tip is not the pin. From lane openRepoShape-5's read of templates/assembly-root/scripts/bootstrap.py: a tracking branch behind the pin is fast-forwarded to it, and a fresh clone's branch past the pin with no commit of its own is moved back to it, sobump-leg.pythenmake bootstrap, andgit clone --recurse-submodulesthenmake bootstrap, leave the leg on its branch AT the pin, no longer detached undernot moving an existing branch(#475) #490 and the review of templates/assembly-root/scripts/bootstrap.py: a tracking branch behind the pin is fast-forwarded to it, and a fresh clone's branch past the pin with no commit of its own is moved back to it, sobump-leg.pythenmake bootstrap, andgit clone --recurse-submodulesthenmake bootstrap, leave the leg on its branch AT the pin, no longer detached undernot moving an existing branch(#475) #490. - → rides with bootstrap.py: a commit on a detached leg, the
not moving an existing branchtext, and what bump-leg.py and AGENTS-shape.md still promise (one re-pin) #533 bootstrap printschecked out, and then git said:(templates/assembly-root/scripts/bootstrap.py:251) over a checkout git refused when HEAD was already on the branch at the pin, for example behind a staleindex.lock. Nothing was checked out, so the line should say git refused a checkout that would have changed nothing; git's own lines after it stay useful, because the lock also blocks the person's next commit. Nit 1 of the verification of templates/assembly-root/scripts/bootstrap.py: a tracking branch behind the pin is fast-forwarded to it, and a fresh clone's branch past the pin with no commit of its own is moved back to it, sobump-leg.pythenmake bootstrap, andgit clone --recurse-submodulesthenmake bootstrap, leave the leg on its branch AT the pin, no longer detached undernot moving an existing branch(#475) #490. Fixed by bootstrap.py: a leg detached off the pin is left where it is and said, so a commit made there is no longer stranded when its branch is already at the pin, thenot moving an existing branchline names where the checkout is, and bump-leg.py and AGENTS-shape.md say "or says why not" (#533) #556 (64fc9a86): with nothing checked out it now printsnothing to check out, and then git said:. - → Printed commands are POSIX-only: quote the bare paste lines in bump-leg.py and update-shape.py, and say in the docs that pasted commands are for a POSIX shell #532 Every command the tools print outside the doctor is quoted for a POSIX shell, so a Windows root whose path holds an apostrophe, such as
C:\work\O'Brien\AtlasorC:\Users\Jane O'Neill\Atlas, prints'O'"'"'Brien', which PowerShell does not read as one argument (a root with only blanks or backslashes is fine: a single-quoted string is a PowerShell verbatim string): update-shape.py:2419 (_by_hand) and :2900 (the landing NEXT),shlex.quoteat :3184-3222, theswitch -cline bump-leg.py:--dry-runruns on the project's tracking branch and ends by naming thegit switch -cthe real run needs, so step 1 of "Advancing a leg" prints theold -> newline on a fresh project instead of being refusedbump-leg-on-tracking-branchbefore it (#477) #484 added to bump-leg.py, the repair line shape-doctor.py and validate-pins.py: a gitlink that is not its pin is offered both repairs,bump-leg.py --tothe gitlink's commit to keep the move and--tothe pin's to undo it, so a gitlink committed by hand no longer ends at a gate t (#478) #495 added to the shipped validate-pins.py, adopt-project.py:3938 and :4799, scripts/family.py (:2847 and the hand fixes at :3216-3277) and the shipped validate-family.py (the root at :733 and :1071, a member path at :1055 and :1069); only shape-doctor.py'squote_arghas a PowerShell alphabet (:551-600) and its own lines are right for PowerShell, the tests round-trip each line throughshlex.split, and update-shape.py:2413-2418 calls the POSIX-only choice deliberate ("Git Bash is one, cmd.exe and PowerShell are not"). The shipped validators reach a project only throughupdate-shape.py, so a change there is a re-pin for every project. Decide one rule: one shared quoter that spellsntPowerShell's way, or say in the docs that these lines are for a POSIX shell. (Line numbers are main 7a8b981's; later landings moved some.) From the review of bump-leg.py:--dry-runruns on the project's tracking branch and ends by naming thegit switch -cthe real run needs, so step 1 of "Advancing a leg" prints theold -> newline on a fresh project instead of being refusedbump-leg-on-tracking-branchbefore it (#477) #484 (codex, thread 4253285979) and of shape-doctor.py and validate-pins.py: a gitlink that is not its pin is offered both repairs,bump-leg.py --tothe gitlink's commit to keep the move and--tothe pin's to undo it, so a gitlink committed by hand no longer ends at a gate t (#478) #495 (codex, finding 3 of the review). - → Printed commands are POSIX-only: quote the bare paste lines in bump-leg.py and update-shape.py, and say in the docs that pasted commands are for a POSIX shell #532 update-shape.py still prints four commands or paths for pasting with no quoting, the defect update-shape.py: every NEXT line a run prints quotes its root, upstream,
--accept-localpaths, branch and lane trailer withshlex.quote, socheck'sapply,apply --branch'sgit -C <root> push && gh pr createandapply's owngit -C <root> checkout -b && git -C <root> commitpaste whole from a root undermy projects/,it's here/or$HOME/(#325) #374 fixed for its NEXT lines (re-checked on main 7ab11cf): theupdate-branch-existsremediation'sgit -C <root> checkout <branch>(update-shape.py:2033-2034),check'sADDED UPSTREAMlist of--add <path>(:1759),apply'spass --add <path>note (_note_untaken_additions, :2618) and theupdate-local-driftremediation's--accept-local <path>(_refuse_local_drift, :2585). Measured on 45e01f4 and on update-shape.py: every NEXT line a run prints quotes its root, upstream,--accept-localpaths, branch and lane trailer withshlex.quote, socheck'sapply,apply --branch'sgit -C <root> push && gh pr createandapply's owngit -C <root> checkout -b && git -C <root> commitpaste whole from a root undermy projects/,it's here/or$HOME/(#325) #374's head merged with it: with the root undersp ace,it'sord$x, the pastedgit -C … checkoutexits 128 or is a shell syntax error, and--add notes/shape notes.mdpasted as printed is refusedunrecognized arguments: notes.md. From update-shape.py: every NEXT line a run prints quotes its root, upstream,--accept-localpaths, branch and lane trailer withshlex.quote, socheck'sapply,apply --branch'sgit -C <root> push && gh pr createandapply's owngit -C <root> checkout -b && git -C <root> commitpaste whole from a root undermy projects/,it's here/or$HOME/(#325) #374's "Left out" and update-shape.py:check's andapply's NEXT lines print the root path unquoted wherelanding_failed's and the rollback hand-fix lines quote it withshlex, so a root under a path with a space pastes as a different command, and none of them quotes for cmd.exe or PowerShell #325's writer (draft U2, kept here under the v1.0.0 scope rule rather than filed).
Known limits found by lane openRepoShape-3's acceptance passes (standard 36538fd, each re-checked on main 7a8b981) and kept here rather than filed:
- → bump-leg.py: a root on
+xis toldpush -u origin +x(forced), a branch named like a tag passesbump-leg-on-tracking-branch,bump-leg-root-dirtyprints C1 controls raw, andcommitted <sha>:lists paths the commit lacks #672, update-shape.pyapply: a rollback line fails over a read-onlycontracts/unsaid,committed on <branch>:lists a path the commit lacks,--branch v1.0.0cannot push beside a tagv1.0.0, and a quoted"files":header is refused #674 AT-scaffold N9: bump-leg.py'scommitted <sha>: <paths>line (:1491) and update-shape.py'scommitted on <branch>: <paths>(:2743) list the paths staged, not the ones the commit changed, and in the repair case (bump-leg.py --toa gitlink that was moved by hand) theold -> newline reads the gitlink, so it saysX -> Xwhile the pin moves. - Closed 2026-10-14, obsolete: Brett Heap's "Stop pre-filling it" on update-shape.py
check's NEXT, and the doctor'sshape currencyrow that sends the reader to it, already carry--accept-local <path>for every locally edited shape copy, so pasting the command AGENTS.md says to run as named re-pins the project's edit as the standard: no refusal, no question to the human, a greenvalidate-pins.pyand a COMPLIANT doctor, though AGENTS.md says never to pass--accept-localand that keeping the edit is the human's decision #501, landed as 8beb3d9 (update-shape.py and shape-doctor.py: no NEXT line pre-fills--accept-localfor a locally edited shape copy, so theapplya row names, pasted, meetsupdate-local-driftand its question to the human instead of re-pinning the edit as the standard, and ashape/collision copy merged into its original and removed ismerged-awaytocheck, whoseapplydrops its pin row behind the same yes, so an adopted root that did its follow-up validates green again (#501, #502) #512). AT-family N2: after a person hand-merges abothfile and commits it,check's NEXT still lacks that file's--accept-local(update-shape.py:1705 builds it from the locally-modified rows only), so the line pasted as printed is refusedupdate-conflictagain and only AGENTS.md's step 4 (and the refusal's own text at :2531-2535) gets the person through; decide it together with update-shape.pycheck's NEXT, and the doctor'sshape currencyrow that sends the reader to it, already carry--accept-local <path>for every locally edited shape copy, so pasting the command AGENTS.md says to run as named re-pins the project's edit as the standard: no refusal, no question to the human, a greenvalidate-pins.pyand a COMPLIANT doctor, though AGENTS.md says never to pass--accept-localand that keeping the edit is the human's decision #501, which is about the same line. - → update-shape.py: four messages (a commit line when there is nothing to commit, a source path nothing names, the commit message's hand-edit claim, a bare
gh pr create) #535 AT-family N3: theupdate-conflictremedy says to read the upstream bytes withgit show <target>:<source path>(update-shape.py:2531-2535), but neither the refusal norchecksays what the source path of the file is (for a project'sscripts/validate-manifest.pyit istemplates/assembly-root/scripts/validate-manifest.py). - → update-shape.py: four messages (a commit line when there is nothing to commit, a source path nothing names, the commit message's hand-edit claim, a bare
gh pr create) #535 AT-family N4: the commitapplywrites (update-shape.py:2005-2012) says "the copies are not hand-edited and the digests are recomputed", and never names a file--accept-localre-pinned from the project's own bytes, so the history carries a claim that is false for exactly those files. - → update-shape.py: four messages (a commit line when there is nothing to commit, a source path nothing names, the commit message's hand-edit claim, a bare
gh pr create) #535 AT-family N7: the baregh pr create --repo <org>/<Project> --base <branch> --head <branch>thatapply --branchprints without--pr(update-shape.py:3195-3200) carries no--title,--bodyor--fill;gh pr create --helpsays a prompt asks for them, so in a shell with no terminal the pasted line likely stops there. Not run (a realgh pr createis never run in these probes).
Activity
apply --add <dir>/<file>where the root holds a plain FILE at<dir>(or a directory it cannot write) dies in aNotADirectoryError(orPermissionError) traceback fromRollback.write_addedafter the pinned copies were written, and nothing puts them back, becausecmd_applyrolls back only onRefusalandCommandFailed#285applyrewrites a pinned copy,contracts/shape-pin.yamlor the manifest THROUGH a symlink the root keeps at that path (Rollback.writeopens itr+b), so the standard's bytes land at the link's end, outside the root if that is where it points, with exit 0, and--branchreports a commit holding a file that commit does not contain #286read_pinand the pinned validate-pins.py and validate-family.py: iteratepin.get("files") or []without asking for a list, so a shape pin withfiles: 5orfiles: trueends shape-doctor.py,update-shape.py checkandapplyand the project's own validator inTypeError: ... is not iterable, exit 1, with no refusal naming the pin #290check's andapply's NEXT lines print the root path unquoted wherelanding_failed's and the rollback hand-fix lines quote it withshlex, so a root under a path with a space pastes as a different command, and none of them quotes for cmd.exe or PowerShell #325.github/workflows/directory or a leg pin that this user cannot open ends the run in a PermissionError traceback, exit 1, under--dry-runas well, becausecmd_bumpandplan_workflowsread them with no handler andmain()catchesRefusalandCommandFailedonly, so no refusal names the file or says to make it readable #383make bootstrap,bump-leg.pyandupdate-shape.py applyrun the project's ownvalidate-pins.pyin the operator's environment, so for a project that pins a neutral product with no local checkout of it each one callsgh api repos/<org>/<product>/git/trees/<sha>with the operator's token, thoughbump-leg.pysays "a project runs its own gate offline" andupdate-shape.pythat "applyis otherwise offline": the three callers #297 named and left #411applyoverwrites a pinned copy,contracts/shape-pin.yamland the manifest in place (Rollback.writeopens the pathr+b), so one kept as a HARD link is rewritten through its other name, a file outside the root (or acp -alsnapshot of the whole root) that nocheckshowed and no pin covers, while the README, the one path guarded withst_nlink > 1, is refused #422_ensure_root_clean, scaffold-project.py_shape_revisionand family.py_shape_snapshotreadgit status --porcelainthroughgit_out, which decodes strictly, so a file name that is not UTF-8 undercore.quotePath=falseends them in a UnicodeDecodeError traceback, exit 1, where bump-leg owesbump-leg-root-dirty(exit 2) and the other two owe a WARNING that the standard's checkout is dirty #423Rollback.undorestores each rewritten file with a barewrite_bytesand removes what--addmade withrmdirandunlink, none under a handler, so an apply that fails after its first write and then cannot put one file back (a copy that became unwritable, a full disk, a lock) ends in an OSError traceback, exit 1, with the refusal that started the rollback lost and the paths after it never attempted, leaving a re-pinned manifest over a restored pin #428checked_branchrunsgit check-ref-format --branch <name>with no cwd and no git-dir, soapply --branch shape/update-xstarted from a directory git cannot open (a.gitfile naming a missing gitdir, a malformed.git/config, a directory removed under the shell) is refusedupdate-branch-invalidfor a good name, git's failure read as the name's fault, where #355 asks the same question of no repository for the scaffold and the family holder #431apply --branchjudges the pin, the manifest and the copies from the working tree but commits HEAD plus only the paths it wrote, so over an uncommitted in-placeapplyit exits 0 with a commit whose ownvalidate-pins.pyis red, dies on git's "nothing to commit", or says "nothing to do" and makes no branch #436rewrite_shape_pinandrewrite_manifestfindfiles:andshape:only as the whole line, so a trailing comment on either, which the reader and both validators accept, is refused asupdate-pin-unrecognised("0filesline(s)") orupdate-manifest-no-shape("has noshape:block", "add it") aftercheckhas sent the person toapply#437--dry-runreturns beforerewrite_pinruns unless nothing moves, so a leg pin the validators accept and the rewrite refuses (digests: {tree_sha256: …}) gets a dry run that printsold -> newand exits 0, and a real run that exits 2bump-leg-pin-unrecognised#438brettheap commentedon Oct 12, 2026
Lane: openreposhape-5 (openRepoShape-5)
(lane openRepoShape-5, 2026-10-12T21:26Z) One item for this area's checklist, from the independent review of #456 (#391) at d0aea3e. It is not in
v1.0.0's scope: it needs a project that has edited its own copy of the shared library and then declinedcheck's merge-by-hand.Please fold this line into the checklist:
--accept-local scripts/repo_shape.pyline, now update-shape.pyapply --accept-local scripts/repo_shape.pycan keep an old helper that a newly copied file imports from, andmake bootstrapthen fails with ImportError #536. Both roots' scripts/bootstrap.py:mainescapes what stdout cannot encode and spells each value it prints, soname: "a\ud83db"or日本underPYTHONIOENCODING=asciino longer endsmake bootstrapin a traceback (#391) #456 movesspelled()andescape_unencodable_stdout()intoscripts/repo_shape.py, and both roots'bootstrap.pyimport them. A project that keeps a locally editedscripts/repo_shape.py(checkreads itboth) withapply --accept-local scripts/repo_shape.py, while taking the newbootstrap.py, gets anImportErrorfromscripts/bootstrap.py,--helpandmake bootstrap. Meanwhileapply's validators pass,checkthen reads everything unchanged, and the project's CI never runs bootstrap. Reproduced with a real scaffolded project. Followingcheck's merge-by-hand avoids it. Direction:applyrefuses a kept--accept-localcopy that lacks a name another copied shape file imports from it.make bootstrap, leaves the leg detached: bootstrap never fast-forwards a local tracking branch that is behind the new pin, though AGENTS.md, README and bump-leg.py say it re-places it #47533 remaining items
brettheap commentedon Oct 16, 2026
(lane openRepoShape-4, 2026-10-16T11:38:10Z) One line for this checklist, from R342's review of #636 (#342), which is now on main as
c22da12. R342 read it on the merged tree and did not run it; #636 does not fix it.+xis toldpush -u origin +x(forced), a branch named like a tag passesbump-leg-on-tracking-branch,bump-leg-root-dirtyprints C1 controls raw, andcommitted <sha>:lists paths the commit lacks #672, update-shape.pyapply: a rollback line fails over a read-onlycontracts/unsaid,committed on <branch>:lists a path the commit lacks,--branch v1.0.0cannot push beside a tagv1.0.0, and a quoted"files":header is refused #674update-shape.py's andbump-leg.py'srun_validatorstill start the root's own validator withsiteon.update-shape.py:2206-2220andscripts/bump-leg.py:1000-1017on the merged tree (shape-doctor.py: every validator child starts with-S, so no.pthin the user's or a virtualenv's site-packages stands in for therepo_shape.pya root lost, and themanifestrow is the gate's ownModuleNotFoundError(#342) #636 on main4cf5fb07): both run[sys.executable, script]with an environment copied whole fromos.environ(scripts/shape_materialize.py:256,276). So shape-doctor.py: after #238 the doctor runs a root's OWN validator with the operator'sPYTHONPATHstill in the child's environment, so for an operator who exports one that reaches arepo_shape.pythemanifestrow of a root that lost its ownscripts/repo_shape.pysaysok ... passesagain, the false pass #238 removed, where the project's own gate in CI fails onModuleNotFoundError: No module named 'repo_shape'#306'sPYTHONPATHroute and shape-doctor.py:run_validatorstarts a root's OWN validator under the doctor's interpreter withsiteprocessing on, so a.pthin the operator's user site or in a virtualenv's site-packages that names a directory holdingrepo_shape.pystands in for the copy the root lost, and themanifestrow saysok ... passesfor a gate that fails in CI onModuleNotFoundError: the false pass #238 removed and #306 closes forPYTHONPATHalone #342's.pthandusercustomizeroutes both still reachapply's rollback gate andbump's validator gate, where the doctor's children now start with-S. Read, not run.applyrefusescopy-missing, so whether a lostrepo_shape.pycan reach its gate at all is the first thing to measure (R342 Tracking: update-shape.py and bump-leg.py, keeping a project's shape copies and leg pins current #458).Lane: openreposhape-4 (openRepoShape-4)
bump-leg.py and setup-project.py: the `git switch -c` line says under…
brettheap commentedon Oct 16, 2026
(lane openRepoShape-4, 2026-10-16T14:10:49Z; edited 2026-10-16T14:28:42Z) These are 6 of lane 4's 23 checklist lines on this issue, from 1 of its comments of 2026-10-15: the ones no other lane held when lane 4 read this issue at 13:17Z. The other 17 are held by other lanes' issues, listed in lane 4's triage summary on #473. Five of these six, L4-74 to L4-78, are lines 1 to 5 of lane 5's #704 (v1.1.0), filed at 13:45Z from the same comment. Lane 4's #709, filed later for all six, is closed as its duplicate. #704 defers L4-79 ("stays on #458"), so L4-79 stays here, open. Lane 4's lines on #457 to #461 are numbered together, L4-01 to L4-161, in posting order, so the numbers here have gaps. Each is copied word for word, with the comment it came from. Brett Heap approved lane 4's triage at 2026-10-16T13:36:07Z ("approve all triage recommendations"); a line ticked below is ticked on that word.
refs/heads/is read two ways #704 [L4-74] Every bootstrap placement that changes files OVERWRITES an ignored file at a path the pin tracks, silently: the on-branchmerge --ff-only(bump-leg.py, thenmake bootstrap, leaves the leg detached: bootstrap never fast-forwards a local tracking branch that is behind the new pin, though AGENTS.md, README and bump-leg.py say it re-places it #475), P9 (place_at_pinfrom another branch), the missing-branchcheckout -b, and T7 (bootstrap.py and AGENTS-shape.md: a leg on another branch is placed when its tracking branch is at the pin and left when it is behind, and the family holder's copy still promises placement unconditionally #603). R603 measured it on main and on bootstrap.py: a leg on another branch whose tracking branch is behind the pin is fast-forwarded and checked out as one at the pin already is, and the family holder's AGENTS-shape.md and bump-leg.py say bootstrap places a leg where that loses no commit (#603) #615's head alike, with the ignored file listed ininfo/excludeor in the other branch's.gitignore. Possible fix:--no-overwrite-ignoreon those checkouts and on the merge; git 2.43 has the flag for both. (was issuecomment-6201724792)refs/heads/is read two ways #704 [L4-75] Moving a leg off a branch with a merge in progress (merge --no-commit) ENDS the merge:MERGE_HEADis removed and the staged result is carried onto the tracking branch. This happens in P9, on the missing-branch path and in T7. A cherry-pick or revert in progress should go the same way when its staged file does not block the checkout; that is reasoned only. Possible fix: refuse to move off a branch with an operation in progress, and say so. (was issuecomment-6201724792)refs/heads/is read two ways #704 [L4-76] P9's line (on <branch> at <pin> (branch tip == pin)) does not name the branch HEAD left. T7's does, after bootstrap.py: a leg on another branch whose tracking branch is behind the pin is fast-forwarded and checked out as one at the pin already is, and the family holder's AGENTS-shape.md and bump-leg.py say bootstrap places a leg where that loses no commit (#603) #615's fix round 1. (was issuecomment-6201724792)refs/heads/is read two ways #704 [L4-77] A tracking branch AHEAD of the pin with no commit of its own (its tip is its upstream's remote-tracking ref) is left withnot moving an existing branchwhenever HEAD is on the branch, on another branch, or on a branch with no commit yet.move_back_to_pinstarts only from HEAD detached at the pin. Moving it back would lose no commit, sotemplates/assembly-root/AGENTS-shape.md:95-96("places each leg ... where that loses nothing") is false of these states. (was issuecomment-6201724792)refs/heads/is read two ways #704 [L4-78] HEAD as a symbolic ref outsiderefs/heads/: P9 places the leg, while T7 printswhich is neither. This is exotic, and byte-identical to main. (was issuecomment-6201724792)current != refs/heads/<branch>compare is case-sensitive. A branch that differs from the tracking branch only in case could take the T7 path, andbranch -fwould then write the loose ref file both names share. (was issuecomment-6201724792)Lane: openreposhape-4 (openRepoShape-4)
update-shape.py: `current_branch` and `Rollback.new_branch` read a br…
brettheap commentedon Oct 17, 2026
(lane openRepoShape-3, 2026-10-17T11:51:28Z) One line for #458, from lane 2's review of #802 (#675), issuecomment-6233233871. It is outside #675's scope and already wrong on main
bf1f099b.update-shape.py's kept-copy check reads a handler that binds and then re-raises as a fallback.except ImportError:/NEW = None/raise.NEW, but Python stops on the re-raisedImportError, so a copy that cannot start is not refused.git show <target>:<source>a remedy prints is shell-quoted, so a source path with a blank or a'pastes; the missing-name refusal says the validators pass only where none of them meets a line; and its readers skip anif TYPE_CHECKING:,if False:orif 0:body and a statement after araise, as Python does (#675) #802's_reachedin place, a handler whose reached body ends in araisecould answer nothing.Lane: openreposhape-3 (openRepoShape-3)
brettheap commentedon Oct 17, 2026
(lane openRepoShape-2, 2026-10-17T12:00:49Z)
One line from rv766's independent review of lane 2's PR #814 for #766, at
0e516bba. #814 spellsbump-leg.py's root throughshown_pathin three refusals and the dry run's branch line. The commands they tell a person to paste are left as they are, for the reason given in #459's L2-104. Per the v1.0.0 rule, an edge case goes on its area's tracking parent and not into a new issue. #814 lands only once this line is here.init's plan rows, seven refusals and bump-leg's root name each path asshown_pathnames it, so an ESC or a line break in a holder, folder, lock, local remote or root no longer reaches the terminal from those plan rows or those refusals' details #814 review; edge case):bump-leg.py'sswitch -candNEXTlines still name the root raw inside shell quotes. Credited: rv766.0e516bba:_switch_command(scripts/bump-leg.py:1171,shlex.joinat:1187), inbump-leg-root-detached(:1232), inbump-leg-on-tracking-branch(:1242), and in the dry run'sthe real run needs a branchline (:1807). That last line names the root spelled and then raw, on the same line.NEXT git -C <root> push(:1858).shown_pathspells it. A pathshown_pathprints as it is keeps today's line byte for byte.bump-leg-root-not-a-project,bump-leg-manifest-unreadableandbump-leg-wrong-kind({path},:183,:192,:196), a--rootthe person typed.Lane: openreposhape-2 (openRepoShape-2)
bootstrap.py and AGENTS-shape.md: a leg with an operation in progress…
brettheap commentedon Oct 17, 2026
(lane openRepoShape-4, 2026-10-17T13:19:28Z) One line for this checklist, from #795 (#686). #795 fixes these three cases in
family.pyonly. Lane 1's re-verification asked for the line before #686 closes (#795 issuecomment-6234322825).bump-leg.pystill has the three twins of family.pybump: a mount with a narrowed fetch refspec is refusedmember-to-local-onlyon a false detail, a commit only a tag holds is told "a pin the rest of the world cannot fetch", a mount with nooriginis told to fix the network #686's lines (R613 p03, R613 p10, Tracking: setup, scaffold, family and the shared helpers, creating projects and families #459 line 105), which family.py:bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795 fixed infamily.pyonly. (1) p03: a commit only a tag of the leg's remote reaches, which the leg also holds, is refusedbump-leg-commit-local-onlywith "somebody committed inside the leg's mount and never pushed it … a pin the rest of the world cannot fetch is a root the rest of the world cannot bootstrap" (scripts/bump-leg.py:980-985), untrue there: a fresh clone of the remote holds the commit, and a fetch by sha exits 0 (family.py:bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795 built the state, the commit pushed with its tag alone, and measured it).family.pynow says "a tag of<repository>reaches it, but no branch of<repository>does" and that the way out is a push to a branch; it asksgit ls-remote --tags --refs originand compares object ids, only when a tag the mount holds contains the commit. (2) p10: a leg with no remote namedorigin(git remote rename origin upstream) has its fetch fail (:960-967,fetched = False) and is told "fix the network or the credential and re-run before believing it" (:974-977);family.pyasksgit remote get-url originonce the fetch has failed (exit 2 means none, any other failure is git's) and says the mount has none. (3) Tracking: setup, scaffold, family and the shared helpers, creating projects and families #459 line 105: the leg's fetch,git fetch -q <remote> +refs/heads/*:refs/remotes/origin/*(:960-961), does not prune, so a branch deleted on the remote after the last fetch still counts inon_a_remote_branch(:894);family.py's now carries--prune --no-prune-tags(Brett Heap's word, relayed by lane 5, docs/release-notes/v1.0.0.md: the v1.0.0 release notes, a draft for Brett to amend before he tags, saying what 1.0 contains and promises, how to pin it by commit, what changed on the way grouped by the five tracking areas, and every known limit as the input that triggers it and what the person sees (#472) #473 13:56:50Z), and it keeps the refs the mount's ownremote.origin.fetchmaintains underrefs/remotes/origin/and skips its negative refspecs (a narrowing refspec never decides reachability), because lane 1's review (family.py:bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795, issuecomment-6233185697, F1) measured that a bare--prunewith the explicit refspec dropsorigin/pr/7(a+refs/pull/*/head:refs/remotes/origin/pr/*ref) and a hand-fetchedorigin/kept-by-handthat main's plain fetch kept (main:origin origin/kept-by-hand origin/main origin/pr/7; bare prune:origin origin/main; with the mount's refspecs added:origin origin/main origin/pr/7). The same review measured that git's opportunistic update force-overwrites a localfeatthrough a configured+refs/heads/*:refs/heads/*or mirror refspec when the fetch names refspecs of its own (featmoved from 76d7b0e to b4af330, and stayed with--refmap=), which applies tobump-leg.py's fetch too. Direction: change the three together withfamily.py's words and tests (tests/test_family_bump_reachability_words.pyin family.py:bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795 is the template), and decide--refmap=for both tools.Corrected at 2026-10-17T20:15:01Z, when #795 landed as
6f64eea:family.py's fetch SKIPS the mount's negative refspecs, since a narrowing refspec never decides reachability. It does not pass them, as this line first said. That follows lane 1's re-verification of #795's round 2, N1. The rest of the line is unchanged.family.py and shape_materialize.py: the NEXT push line names the bran…
family.py: `bump` fetches every branch of the member's `origin` whate…
family.py, scaffold-project.py and bump-leg.py: `init`'s plan rows, s…
update-shape.py: the `git show <target>:<source>` the remedies print …
brettheap commentedon Oct 18, 2026
(lane openRepoShape-3, 2026-10-18T14:31:02Z) Triage of #458's unchecked items at the v1.1.1 release commit
5017830. This is lane 3's TRIAGE-458, read only.36 items: 31 FIXED and 5 OPEN.
Mainwhose tracking branchmainis behind the pin is moved under its own HEAD, and bootstrap saysit is on main nowover an index that undoes the pin #896, update-shape.py: the kept-copy check reads anexcept ImportError:handler that binds the name and then re-raises as the copy's own fallback, so a copy that cannot start is kept #897 and bump-leg.py: a commit only a tag of the leg's remote reaches is told it was never pushed, a leg with nooriginis told to fix the network, and the fetch does not prune: the three cases #795 fixed in family.py #898, in v1.2.0 and unclaimed.apply --accept-local scripts/repo_shape.pycan keep an old helper that a newly copied file imports from, andmake bootstrapthen fails with ImportError #536, bootstrap.py and AGENTS-shape.md: a leg on another branch is placed when its tracking branch is at the pin and left when it is behind, and the family holder's copy still promises placement unconditionally #603, update-shape.pyapply --accept-local: a kept.pycopy that imports a name the run's newrepo_shape.pydropped passes, andmake bootstrapthen dies on an ImportError #604, bump-leg.py and setup-project.py: thegit switch -cline fails "already exists" when an earlier act left that branch, and nothing says what to do (#607's sentence, in the two tools #607 left) #647, bump-leg.py: a root on+xis toldpush -u origin +x(forced), a branch named like a tag passesbump-leg-on-tracking-branch,bump-leg-root-dirtyprints C1 controls raw, andcommitted <sha>:lists paths the commit lacks #672, update-shape.pyapply: a rollback line fails over a read-onlycontracts/unsaid,committed on <branch>:lists a path the commit lacks,--branch v1.0.0cannot push beside a tagv1.0.0, and a quoted"files":header is refused #674-shape-doctor.py: thepinsrow sayscheck'sapplydrops merged-away rows wherechecksays REFUSAL AHEAD (a pinned copy kept as a link), and theshape currencycounts leave out five ofcheck's states #676, bootstrap.py: moving a leg off a branch overwrites an ignored file and ends a merge, P9 does not name the branch it leaves, a branch at its upstream's tip is left when HEAD is on a branch, a HEAD outsiderefs/heads/is read two ways #704) is closed, and so are v1.0.0's sub-issues (update-shape.pyapplyoverwrites a pinned copy,contracts/shape-pin.yamland the manifest in place (Rollback.writeopens the pathr+b), so one kept as a HARD link is rewritten through its other name, a file outside the root (or acp -alsnapshot of the whole root) that nocheckshowed and no pin covers, while the README, the one path guarded withst_nlink > 1, is refused #422, bump-leg.py_ensure_root_clean, scaffold-project.py_shape_revisionand family.py_shape_snapshotreadgit status --porcelainthroughgit_out, which decodes strictly, so a file name that is not UTF-8 undercore.quotePath=falseends them in a UnicodeDecodeError traceback, exit 1, where bump-leg owesbump-leg-root-dirty(exit 2) and the other two owe a WARNING that the standard's checkout is dirty #423, update-shape.pyRollback.undorestores each rewritten file with a barewrite_bytesand removes what--addmade withrmdirandunlink, none under a handler, so an apply that fails after its first write and then cannot put one file back (a copy that became unwritable, a full disk, a lock) ends in an OSError traceback, exit 1, with the refusal that started the rollback lost and the paths after it never attempted, leaving a re-pinned manifest over a restored pin #428, update-shape.pychecked_branchrunsgit check-ref-format --branch <name>with no cwd and no git-dir, soapply --branch shape/update-xstarted from a directory git cannot open (a.gitfile naming a missing gitdir, a malformed.git/config, a directory removed under the shell) is refusedupdate-branch-invalidfor a good name, git's failure read as the name's fault, where #355 asks the same question of no repository for the scaffold and the family holder #431, update-shape.pyrewrite_shape_pinandrewrite_manifestfindfiles:andshape:only as the whole line, so a trailing comment on either, which the reader and both validators accept, is refused asupdate-pin-unrecognised("0filesline(s)") orupdate-manifest-no-shape("has noshape:block", "add it") aftercheckhas sent the person toapply#437, bump-leg.py--dry-runreturns beforerewrite_pinruns unless nothing moves, so a leg pin the validators accept and the rewrite refuses (digests: {tree_sha256: …}) gets a dry run that printsold -> newand exits 0, and a real run that exits 2bump-leg-pin-unrecognised#438).5017830--toan annotated tag's id7d9bec9(#561):bump-leg.py:884peeled_targetapplycommit linea35f1be(#558):update-shape.py:5642-5646709eec1(#588):bump-leg.py:1271,:194264fc9a8(#556),af01068(#505),2e6c6fa(#559):bump-leg.py:1929-1931,AGENTS.md:359,shape-doctor.py:7339-7341709eec1(#588), on Brett Heap's "README; doctor exempt":README.md:158-159709eec1:update-shape.py:2251,:2662,:3839,:3858fc86720(#746):bump-leg.py:1758;d17564d(#810):update-shape.py:4768-4771a35f1be(#558):update-shape.py:3755,:2690-2697,:5498-5504repo_shape.pylacks a namebbcfd34(#562):update-shape.py:412964fc9a8:bootstrap.py:805-809,:1099-110177bb5f0(#615): family-rootAGENTS-shape.md:70-72;bootstrap.py:880-887"files":headerd17564d(#810):update-shape.py:1478-1483--dry-runover refused validators exits 0fc86720:bump-leg.py:1875-1882. The behaviour is unchanged and stays a known limit.pyimports a dropped nameb9f65be(#616):update-shape.py:3883,:4402contracts/; mutant test gapsd17564d:update-shape.py:3035-3042; tests attest_update_shape_apply_lines_and_headers.py:9-15;0d5d2b1:test_update_shape_kept_helper.py:9200d5d2b1(#802):update-shape.py:3724-3735,:4094-4127,:4361,:4376. The one-hop check is a v1.1.1 known limit, meant to stay24a977c(#745):bootstrap.py:568,:692,:805-809,:821-822,:1038; assemblyAGENTS-shape.md:100-101(wording only); familybootstrap.py:79-82refs/heads/<b>comparebootstrap.py:880-881, emulated probe in #896+xpush, tag-named branchfc86720:bump-leg.py:1245-1249,:1942-1944,:1127-1137;d17564d:update-shape.py:4977-4992run_validatorstarts withsiteonbump-leg.py:1067-1070,update-shape.py:2320-2321. #672 and #674 both found that no verdict differs. Lane 3 leaves it for Brett Heap: won't-change, or an issue on his wordupdate-shape.py:4328-4337, probe in #897bump-leg.py:1271,:1942,:188,:197,:201, confirmed by probe. Ruling 5: an edge case, taken only if a real user meets itbump-leg.py:998,:1013-1014,:1027-1039Rows 11 to 37 are lines in this issue's comments. Those comments are left as their authors wrote them.
Lane: openreposhape-3 (openRepoShape-3)
brettheap commentedon Oct 23, 2026
(lane openRepoShape-5, 2026-10-23T12:05:18Z)
Lane 5 filed one follow-up that belongs to this tracker. It comes from its independent review of #1166 (#1139), findings 3 and 9 (#1166 issuecomment-6352173084), and was re-measured at main
961640c6.source_repository:that names a path is refused as--upstream, and with--upstreampassed one that ends in a line feed is never asked and reaches the commit subject and the pull request title (#1166 follow-up) #1203:update-shape.pyasks a shape pin'ssource_repository:on neither route:--upstream, a flag nobody passed;--upstreampassed, a value that ends in a line feed is never asked, and it reaches the commit subject and the pull request title.It is a post-tag follow-up, with no milestone.
Lane: openreposhape-5 (openRepoShape-5)