Repository navigation
Every Path.is_file() / is_dir() / exists() guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow @<sha> check, update-shape.py says copy-missing and update-pin-missing ("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430
Description
(lane openRepoShape-2, 2026-10-12T17:30Z)
Found by the writer of lane-2 PR #403 (the fix for #383), whose CI ran on Python 3.14 and met a guard that reads "unreadable" as "absent"; this issue is the sweep of the other tools. Reproduced on origin/main 92e29d9 (git 2.43.0) under Python 3.12.3 and 3.14.7 on the same trees, local remotes only, a stub gh, as a non-root user. Not claimed.
What is wrong. Up to Python 3.13, Path.is_file(), Path.is_dir(), Path.exists() and Path.is_symlink() answer False only for a path that is not there (ENOENT, ENOTDIR and a few more) and RAISE PermissionError for the rest. On 3.14 they answer False to ANY OSError. (os.path.exists, isfile, isdir and lexists have always done that.) The standard's CI runs 3.x, which is 3.14 on all three OSes, and its baseline is 3.9, so one source has two behaviours. A directory this user cannot search (mode r-- or ---), or any path whose stat fails for another reason, therefore reads as ABSENT on 3.14, and every guard of the form if not path.is_file(): <say it is missing, or skip> now says a false thing about the person's repository or skips a check, where on 3.12/3.13 it raised a PermissionError traceback (the shape #383 files for bump-leg, and #339 for --pin-source):
3.12.3: Path.exists() RAISED PermissionError; Path.is_file() RAISED PermissionError; Path.is_dir() RAISED PermissionError; os.path.isfile=False
3.13.15: Path.exists() RAISED PermissionError; Path.is_file() RAISED PermissionError; Path.is_dir() RAISED PermissionError; os.path.isfile=False
3.14.7: Path.exists()=False; Path.is_file()=False; Path.is_dir()=False; os.path.isfile=False (a file under a directory with mode 000)
A traceback is loud; a wrong answer is not. Run on the same tree (a scaffolded root, a path chmod'd), the same command gives different words:
| the root's path | tool | Python 3.12 | Python 3.14 |
|---|---|---|---|
contracts/ mode 000 or 444 |
scripts/validate-pins.py (the project's own gate) |
PermissionError traceback |
REFUSED pin-missing: leg 'spec' declares testorg/Atlas-spec at 'spec' but contracts/spec-pin.yaml does not exist |
| the same | update-shape.py check |
traceback | REFUSED update-pin-missing: ... contracts/shape-pin.yaml does not exist, remediation "A project without a shape pin is adopted with adopt-project.py, not updated" |
| the same | shape-doctor.py --root |
DRIFTED (2 leg(s) not at the pin) |
CANNOT ANSWER (shape currency: the copies cannot be compared ...) with a git fetch NEXT |
.github/workflows/ mode 444 (listable, not searchable) |
scripts/validate-pins.py |
traceback | FINDING shape-copy-missing: ... .github/workflows/validate.yml is pinned but absent, and _workflow_refs returns NO references: the @<sha> lockstep check of every workflow file is skipped |
| the same | update-shape.py check |
traceback | copy-missing .github/workflows/validate.yml (pinned but absent from the root; a human decides whether it was deleted on purpose) |
| the same | shape-doctor.py --root |
INVALID (pins) |
DRIFTED (1 copy-missing) |
scripts/ mode 000 or 444 |
update-shape.py check |
traceback | copy-missing scripts/validate-pins.py, and four more |
| the same | update-shape.py apply --yes |
traceback | REFUSED update-conflict: ... pinned but absent from the root; a human decides whether it was deleted on purpose |
| the same | shape-doctor.py --root |
INVALID (manifest, pins) |
DRIFTED (5 copy-missing) |
spec/ (a mount) mode 000 |
scripts/validate-pins.py |
traceback | REFUSED pin-submodule-uninitialized: spec is not an initialized submodule checkout |
.github/workflows/ mode 444 |
bump-leg.py --dry-run |
traceback | exit 0: the workflows are not looked at |
| every row, nothing chmod'd (controls) | the five tools | ok / COMPLIANT / nothing to do |
the same |
None of the 3.14 answers is true: every file named "absent" is there, and a person who follows the remediation (restore it, adopt the project, decide whether it was deleted on purpose) is sent after the wrong thing. chmod u+x on the directory is the cure and nothing says so. The silent half: on 3.14 validate-pins.py's _workflow_refs returns [] for the unsearchable workflows directory, so the gate that is meant to refuse a workflow whose @<sha> reference is stale passes it, and a tool that skips a check when its script is "absent" skips it (see the sites below).
The sites, at origin/main 92e29d9. Each is a Path.is_file(), is_dir(), exists() or is_symlink() guard, or an os.path twin, on a path the person can make unreadable. Marked R where the table above ran it, C where it is read from the code and not run.
- A check is skipped as "absent" (the worst: no output at all):
templates/assembly-root/scripts/validate-pins.py:117,:120_workflow_refs(R);scripts/bump-leg.py:561run_validator("is absent; SKIPPED", C),:394and:400plan_workflows(R: exit 0 on 3.14);update-shape.py:1638run_validator, the same sentence forapply's post-write validators (C: shadowed inapplyby the falseupdate-conflictbelow, so not seen skipping).
- A false "does not exist" / "pinned but absent" finding or refusal, or a NEXT dropped as if there were nothing to run (R unless noted):
update-shape.py:389read_pin(update-pin-missing),:494classify(copy-missing, whichCONFLICTS(:246) turns into anupdate-conflictrefusal ofapply),:1299the README reader (C),:223the kind of root (C);templates/assembly-root/scripts/validate-pins.py:137(pin-missing),:293(shape-pin-missing),:325(shape-copy-missing),:524(neutral-pin-missing, C),:735(manifest-missing, C);templates/assembly-root/scripts/validate-manifest.py:259(naming-referent-missing, C),:680(manifest-missing, C);templates/family-root/scripts/validate-family.py:405,:447,:492,:772(C);scripts/bump-leg.py:137Project.open(C),:759_leg_pin_path(R,bump-leg-pin-missing);shape-doctor.py:1022validator_for(an own copy that cannot be read selects the standard's TEMPLATE validator, so the row audits the template, C),:1114,:1202,:1767,:1791,:2103,:2107,:3162(theagent filesrow) (C). - A mount or repository read as "not a checkout":
bump-leg.py:717_initialized_submodule(R),validate-pins.py:212(R),:463,shape-doctor.py:1941,:1965,:2131,:2553,:3215,:3238,:3274,:3477,:3581,:3803,:4109,templates/*/scripts/bootstrap.py:109,:138andtemplates/family-root/scripts/siblings.py:436,:597,:844,templates/family-root/scripts/validate-family.py:327,scripts/family.py:2263,scripts/shape_advisory.py:438,:714,scripts/repo_shape.py:2435(C). The effect is accurate (a mount that cannot be entered cannot be used) and the cause named is not. - A destination read as free, or a cleanup skipped:
scaffold-project.py:253,:1050;adopt-project.py:718,:3450;scripts/family.py:636,:854,:1393,:1666,:1669,:1746,:1750,:2533;setup-project.py:1676;scripts/shape_materialize.py:671,:676,:1794;templates/family-root/scripts/siblings.py:437,:765(C). Most end in a loud failure at the create;family.py:1746(_undo_tree, the rollback of a mount, askstree.exists()before it removes the tree) and:1750decide a cleanup and are the ones to read first. - The
os.pathtwins, which answer False to anyOSErroron every version and so are the same class but not a 3.14 change:shape-doctor.py:3385,templates/family-root/scripts/siblings.py:258,templates/assembly-root/scripts/validate-pins.py:594,setup-project.py:1676,update-shape.py:844,scripts/family.py:1176,:1242,:1345,:2477,templates/family-root/scripts/validate-family.py:677(C).
PR #403 (fix for #383) has fixed three of bump-leg.py's guards: fab496f on fix/bump-leg-unreadable-oserror adds _is_a(path, kind), which stats the path itself and answers absent only for FileNotFoundError and NotADirectoryError, and uses it for the workflow files, the workflows directory and the pin. At that head the same file still has Project.open (.is_file(), :159), run_validator (:853) and _initialized_submodule (.exists(), :1009); this issue is those and every other tool.
What it should do.
- One helper, asked wherever a guard must tell "absent" from "unreadable":
statthe path itself;FileNotFoundErrorandNotADirectoryErrorare absent, any otherOSErroris not an answer. Take_is_a(PR bump-leg.py: a workflow file, the workflows directory or the leg's pin that this user cannot open is refused by name with a remedy, so the run ends in exit 2 and not a PermissionError traceback (#383) #403) as the model and put the one definition where every tool can import it (scripts/repo_shape.py, which every tool and every project's copy already imports; a change there moves its sha, so every project seesupstream-changedand takes it withupdate-shape.py, as it must for the three validators below in any case). - An unreadable path is a refusal that says so:
<path> cannot be read: [Errno 13] Permission denied, with a remediation that names the cure (chmod u+x <dir>, or run as a user that can read it), under the same code family as the absent case, exit 2, and a FINDING with that sentence in the project's own validators (validate-pins.py,validate-manifest.py,validate-family.py, which are pinned copies and reach projects throughupdate-shape.py). The doctor says CANNOT ANSWER only where the question could not be asked of this checkout; an unreadable path in THEIR repository isINVALID-class with the cure, neverDRIFTED (copy-missing). - No guard may skip a check on
False:run_validator(both),_workflow_refsandplan_workflowsrefuse when the path is unreadable. - Group 4's cleanups ask the question the way group 1 does; where a
Falsewas only ever a "go ahead and create", keep it.
How to test. tests/test_bump_leg_unopenable.py (PR #403) is the pattern: a scaffolded root, os.chmod of contracts, .github/workflows, scripts and a leg mount to 000 and 0o444, skipped on Windows and under root, the command run in a subprocess. For each tool in the table above, the cell is asserted to be the SAME refusal on every interpreter, naming the path and the cause and never does not exist, pinned but absent, SKIPPED or copy-missing; validate-pins.py with a stale-@<sha> workflow in an unsearchable .github/workflows must NOT pass. CI's 3.x is 3.14, so the new tests pass on the interpreter that exposes the defect; run the same file under 3.12 locally, where today it raises. A hygiene test beside tests/test_git_output_decoding.py (#321, #335: it reads the shipped tools' source for a call shape that has no business being there) can fail a new .is_file() / .is_dir() / .exists() guard on a user path in a shipped tool unless it goes through the helper or is on a short allowlist of paths the tool itself created.
Not in this issue. Whether a particular guard should refuse or fall back (the author's call per site, with the table above as the evidence). The PermissionError tracebacks on 3.12/3.13 for the same inputs (#383 for bump-leg, #339 for --pin-source, #285 for update-shape.py apply --add): they are the same fix seen from the other interpreter. A path the tool created itself a moment ago.
Related: #383 and PR #403 (bump-leg's three guards, and the model helper); #289 and PR #352; #339 (a --pin-source under a parent this user cannot enter); #285 and PR #346; the review of PR #347 (finding 1: the doctor's check_shape_currency marks every prepare refusal environmental, which is why contracts/ mode 000 reads CANNOT ANSWER with a git fetch NEXT on 3.14).
Lane: openreposhape-2 (openRepoShape-2)
Activity
brettheap commentedon Oct 12, 2026
(lane openRepoShape-5, 2026-10-12T19:07:29Z)
Lane: openreposhape-5 (openRepoShape-5)
An addition for group 3 and for the 3.12/3.13 half this issue leaves to per-tool issues:
templates/assembly-root/scripts/validate-pins.py:463,if (sibling / ".git").exists():, is run, not only read, and before Python 3.14 it ends the project's ownvalidate-pins.pyin aPermissionErrortraceback. Found by lane 5's review offix/link-source-values(the fix for #338), while checking what that fix's reach claims did and did not cover. It is posted here, and not filed apart, because it is the same line this issue lists for 3.14 (group 3, marked C there) and the same helper cures both halves. No per-tool issue covers it: #383 isbump-leg.py, #339 isscaffold-project.py's--pin-source, #285 isupdate-shape.py apply --add.What it does.
resolve_neutral_pin_sourcetries a pin's sources in order:--pin-source,SHAPE_PIN_SOURCE_<PRODUCT>, then a checkout beside the project, then a bare--pin-source. The third is probed with the call above. A sibling this user cannot search (a checkout left at mode 000, or inside a parent they cannot enter) makes(sibling / ".git").stat()fail with EACCES, so before 3.14 the call raises out ofmain: no finding, no skip, a traceback and exit 1. The same file already knows the lesson:_digest_from_sourceasksstat()inside a guard, "and notexists()/is_dir()", but that is applied to the source the probe returns and not to the probe that decides whether there is one. On 3.14 the same call answers False, so the sibling is passed over andgh api, or a skip, answers next: that half is this issue's.Who inherits it. Everything that runs the project's own
validate-pins.py:make validateandmake bootstrap(templates/assembly-root/Makefile:49,templates/assembly-root/scripts/bootstrap.py:79),validate.yml(templates/assembly-root/.github/workflows/validate.yml:200) and the doctor'spinsrow (check_pinsinshape-doctor.py), which then saysINVALID (pins)over a traceback where a finding was meant. #338's fix guards the same sibling in the naming gate and invalidate-manifest.py(throughlink_manifest_lookup), so on a project that has taken it those two steps exit 0 for this row, andvalidate-pins.pyis the step that still dies.Reproduction (local remotes only: no network, and no
ghcall; put a stubghfirst on PATH and setGH_TOKEN=bogus GH_CONFIG_DIR=<an empty dir>to be sure):Observed on origin/main 3ef059c (Linux, non-root; the checkout path is written
<TMP>):The first two are this addition. The 3.14 row is the half this issue already describes: the sibling is passed over, and nothing says a checkout was there and could not be read. (The commit sha is specific to each run.)
What it should do, in this issue's terms.
statthe path itself;FileNotFoundErrorandNotADirectoryErrorare absent, any otherOSErroris not an answer), and apply it at:463. Either reading is acceptable, the author's call: the sibling is the ordinary absent case, as 3.14 does today, or, because a checkout that is there and cannot be read is an environment fault somebody should hear about, anotenaming the path and the system's reason, and then go on. Either way, no traceback, and no change to the exit code of a project whose pins verify some other way.:212(_check_pin_digest:(submodule / ".git").exists()) is the same probe for the project's own leg mount, and group 3 already lists it (R).:476onfix/pin-source-env-collisions(PR scaffold-project.py, validate-pins.py, repo_shape.py:open-glassandopen_glassshare oneSHAPE_PIN_SOURCE_<PRODUCT>, so--pinrefuses the pair andvalidate-pins.pynotes a declared one and reads the variable for neither (#275) #409),:470onfix/adopt-pin-writes-pin-file,:389on PR update-shape.pyread_pinrefusesupdate-pin-row, and the pinned validate-pins.py and validate-family.py reportshape-pin-row, when a shape pin'sfiles:is not a list, sofiles: 5,trueor1.5no longer endscheck,apply, shape-doctor.py and the project's own validator inTypeError: ... is not iterable(#290) #343 and:473on PR validate-pins.py (assembly-root template): a workflow file under.github/workflows/that is not UTF-8, or that this user cannot open, is ONEworkflow-unreadablerefusal naming every such file, exit 2, so the project's pins gate no longer ends in a UnicodeDecodeError traceback, exit 1, with no line naming the file (#359) #419. Whoever lands the helper will meet it in four places, and a branch that reaches main first moves the line.How to test.
tests/test_neutral_pin_validator.pyalready hasSEARCH_DENIED(askipiffor Windows and root) and tests for an explicit source under a locked parent. Add the sibling: a scaffolded project (or a minimalproject.yamlwith a pin and itscontracts/<name>-pin.yaml) with../<repo>at mode 000, restored in afinally, then runvalidate-pins.pyand assert noTracebackand the chosen outcome. As this issue says of its own tests, CI's3.xis 3.14, where the call answers False, so a test of the traceback passes there without the fix: run it under 3.12 or 3.13 locally, where reverting the guard fails it.validate-repository-naming.py, repo_shape.py: a `--link-source` value…
bootstrap.py: a validator's exit 2 ends `bootstrap REFUSED`, exit 2, …
brettheap commentedon Oct 18, 2026
CLAIMED — lane openRepoShape-1, session 300f8f46-004a-4e72-8587-4f192be2e108@Eagle, 2026-10-18T14:13:58Z, for #430
Logged in
lanes/log/openRepoShape-1.mdatfc4b7895497eee3cf694cc0b974a914f9d777afa.The three reads (lane-collision-protocol Rule 1):
brettheap commentedon Oct 18, 2026
(lane openRepoShape-1, 2026-10-18T14:27:20Z) #430's plan at the v1.1.1 release commit
5017830: what is fixed, what is open, one rule, and ten slices with no file in two. Lane 2 picks first.Scoped by lane 1's analyst (opus), read-only.
5017830.sitecustomizethat makesis_file,is_dir,existsandis_symlinkanswer False to anyOSError. Under a mode-000 directory, 3.12 raises and the shim returns False.gh.Fixed since #430 was filed
validate-pins.py's half: templates/assembly-root/scripts/validate-pins.py: a.github/workflows/directory that this user cannot list or search ends the run in a PermissionError traceback, exit 1, where a workflow file it cannot open is aworkflow-unreadablerefusal, exit 2 #434 (PR validate-pins.py (assembly-root template): a.github/workflows/directory that this user cannot list or search is aworkflow-unreadablerefusal naming it, exit 2, so the project's pins gate no longer ends in a PermissionError traceback, exit 1, where a workflow file it cannot open already was one (#434) #633), templates/assembly-root/scripts/validate-pins.py: a workflow file under .github/workflows/ that is not UTF-8 ends the run in a UnicodeDecodeError traceback, exit 1, with no refusal naming the file, so the project's pins gate (make validate, the validate workflow) dies on a Latin-1 comment where a non-UTF-8 pin is a cleanREFUSED yaml-unreadable, exit 2 #359 (PR validate-pins.py (assembly-root template): a workflow file under.github/workflows/that is not UTF-8, or that this user cannot open, is ONEworkflow-unreadablerefusal naming every such file, exit 2, so the project's pins gate no longer ends in a UnicodeDecodeError traceback, exit 1, with no line naming the file (#359) #419) and validate-pins.py: an unsearchablecontracts/still ends in a PermissionError, an unreadable pin is named by its absolute path twice, and a workflow name or a pin entry's control character reaches stderr raw #712 (PR validate-pins.py and repo_shape.py: a pin, the manifest or a shape copy the gate cannot reach or read is refused by name, relative to the root, once and with its cause's advice, and every name and path it prints is spelled (#712) #827).bump-leg.py's file reads: bump-leg.py: a workflow file, a.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 #383 (PR bump-leg.py: a workflow file, the workflows directory or the leg's pin that this user cannot open is refused by name with a remedy, so the run ends in exit 2 and not a PermissionError traceback (#383) #403).contracts/this user cannot read, thelegs,manifest kindsandshape currencyrows disagree with validate-pins.py's named refusal, andmanifest kindsnames agit checkout HEAD --that discards work #847 (PR shape-doctor.py: thelegs,manifest kindsandshape currencyrows name a pin orcontracts/this user cannot read as validate-pins.py does, with its advice and nevergit checkout HEAD --, and the verdict is INVALID, not DRIFTED (#847) #860).siblings.py's not-a-clone: siblings.py (make siblings): a FILE, or a directory it cannot list, at../<Project>ends in a traceback (NotADirectoryErrororPermissionErroratsiblings.py:558), exit 1, where the doctor calls the slot NOT A CLONE #735 (PR siblings.py: a file, or a foldermake siblingscannot list or enter, at../<Project>is NOT A CLONE, reported and left as it is, so the run no longer ends in a traceback atiterdirbefore the members after it are placed, andmake parkno longer ends in one over a folder it cannot enter (#735) #772) and siblings.py and shape-doctor.py: a dangling Windows junction at../<Project>still reads as an empty slot, because #784's dangling-link rule asks onlyis_symlink(), which is False for a junction; also askos.path.isjunction, asfamily_linkdoes #793 (PR siblings.py and shape-doctor.py: a Windows junction to nothing at../<Project>is NOT A CLONE, as a link to nothing is, so the dry run no longer sayswould cloneand the doctor'smembersrow no longer calls the slotabsent(#793) #794).[declared-unverified]#608 (PR setup-project.py: step (3) refuses a parent nobody can write in and a drive that is not there, step (4) verifies a chain from--pin-source,--pinno longer takes a flag as its value, and step (5) says a printed plan was refused (#608) #623), scaffold-project.py andfamily.py init: the dry runs skip the target-exists and folder checks, and anygh repo viewfailure reads as "does not exist" #609 (PR scaffold-project.py and family.py: both runs ask whether each folder they make can be made and take only gh's not-found answer as "absent", so--dry-runrefuses a file where a folder goes, or agh repo viewthat fails otherwise, by name as the real run does, and a refusedinitleaves no empty family folder (#609) #622) and adopt-project.pyexecute: each leg repository is asked about only as it is made, so a code leg whose name is taken leaves the spec leg created, on GitHub as under--local-remote-dir, and a--local-remote-dirthat is a file or cannot be written ends in a traceback afterchecksaidplan ok#798 (PR adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861).Still open, measured
scripts/444bump-leg.py, a real runbump-leg-rewrite-failed, rolled backscripts/validate-pins.py is absent; SKIPPED, the same forvalidate-manifest.py, thencommitted ...scripts/444INVALID (manifest, pins)DRIFTED (5 copy-missing), andok manifest the standard's templates/...validate-manifest.py passesscripts/444update-shape check/apply --yescopy-missing;apply:REFUSED update-conflict ... pinned but absentcontracts/000update-shape check:599REFUSED update-pin-missing: ...shape-pin.yaml does not exist.github/workflows/444update-shape check/ doctorINVALID (pins)copy-missing/DRIFTED (1 copy-missing)spec/000bump-leg.py --dry-run:1338bump-leg-uninitializedmembers/Atlas000validate-family.py:2932REFUSED member-uninitializedmembers/000DRIFTED, with the members row "could not be run"Fixed now, on both:
contracts/000 undervalidate-pins.py,validate-manifest.py, the doctor and bump's--dry-run;.github/workflows/444 undervalidate-pins.pyand bump;spec/000 undervalidate-pins.py; and../openXdox000.A verdict bug on 3.12 too: with a leg mount at 000, the legs, leg-shape-files and placement rows say "could not be run", yet the verdict is
DRIFTED (1 leg(s) not at the pin).The rule for every slice
OSErroris refused by name:<path relative to the root>: cannot be read (<strerror>), exit 2. The cure ischmod u+rx <dir>(and every directory above it) orchmod u+r <file>. This is the wording ofvalidate-pins.py's_unreadable_file, with itsOTHER_REASONfor causes other than permission.scripts/repo_shape.pyis a shape copy: every project and holder would get anupstream-changedrow, and a pinned validator must not rely on a newer one (repo_shape.py'sRefusalgives every refusal built without a remediation the assembly root's own (run git submodule update --init --recursive, then python3 scripts/validate-pins.py ... contracts/<leg>-pin.yaml), so in a family holderfamily.py addover an unparsable.gitmodules(git-failed),bumpon afamily.yamlof the wrong kind, and the holder's ownvalidate-family.pyon a shortpin.commiteach tell the operator to run a file no holder carries and to move a leg pin a holder does not have #370, Pinned shape copies: AGENTS-shape.md'sCONTRACTS_DIRsentence is false for every scaffolded root, and the chain WARNING offers remedies the scaffold lacks (one shape bump before the v1.1.0 tag) #610). It stays untouched._is_a, asbump-leg.py:413,validate-pins.py:257andshape-doctor.py:3299already do.conftest.scaffolded;chmodcontext infinally, skipped on Windows and as root (tests/test_bump_leg_unopenable.py:106);refused_statmonkeypatch (same file,:573), which behaves as 3.14 on any Python.Slices (sites at
5017830)scripts/bump-leg.py.:1055run_validator, the silent SKIP: check both validator scripts BEFORE anything is written, dry run included. A missing script is still SKIPPED; an unreadable one is refused by name.:1338_initialized_submoduleand:188Project.openraise the existingbump-leg-*-unreadablerefusals.update-shape.py.:599read_pin,:778_classify_row,:2310run_validator,:291root_kind,:2114prepare,:496/:498open_upstream,:719/:721merged_into,:1893, and:1147/:1174. At the last two, the link guard answers "no link" on anOSError.OSErrorout ofread_pinandprepare. The doctor has rendered a raisedOSErrorby name since shape-doctor.py: for a pin or acontracts/this user cannot read, thelegs,manifest kindsandshape currencyrows disagree with validate-pins.py's named refusal, andmanifest kindsnames agit checkout HEAD --that discards work #847.mainrefusesupdate-unreadable.DRIFTED (copy-missing).shape-doctor.py, andscripts/shape_advisory.py:355/:357.:2000validator_formust never run the template for an unreadable own copy.:7247/:7248,:9792/:9793,:7486,:6277, and the low git-directory sites.main.templates/assembly-root/scripts/validate-manifest.py(:406,:1085) andbootstrap.py(:1409, a skip;:1191,:1056,:717/:750and:1328).templates/family-root/scripts/validate-family.py::946,:2932,:1073,:1092,:1134,:1188,:745and:2695. After validate-family.py and bootstrap.py: the holder's remedies, typed as printed in order, put a.gitmodulesgone from the work tree back once and never over another remedy's write, stage it before a gitlink'sgit rmand after a section written back, and name a checkoutgit mvmoved before the update, and bootstrap fails where git's init in this holder stopped with every member checked out, so no remedy undoes another or exits 128 (#879) #889 lands.siblings.pyandbootstrap.py, pinned [S-M]:siblings.py:682(a skip),:967,:447,:567and:1356;bootstrap.py:979/:983(a skip) and:885. After validate-family.py and bootstrap.py: the holder's remedies, typed as printed in order, put a.gitmodulesgone from the work tree back once and never over another remedy's write, stage it before a gitlink'sgit rmand after a section written back, and name a checkoutgit mvmoved before the update, and bootstrap fails where git's init in this holder stopped with every member checked out, so no remedy undoes another or exits 128 (#879) #889 and the family root's bootstrap.py: an(ignored)line, for any code or signal, resets the depth fully, so a member's ownexit 2after a-one or two makes below, or after|| trueand a-, readsexited 2, and the limits name the-klet-fail shape (#890) #891.scripts/family.py::8704,:9538,:9423,:9333,:305/:9530,:7179, and the clean-ups at:6536-:6620and:7198-:7660. Before a removal, absent means ENOENT only.adopt-project.py::1826/:1765,:5735,:8538/:11216and:9017. An unreadable--source, or a recorded source, is refused by name, never treated as "not a path" and never cloned again.tests/test_path_guard_hygiene.py. It fails any new bare guard in a shipped tool, and holds every_is_ato one errno set with a parity test. It lands last, and closes EveryPath.is_file()/is_dir()/exists()guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow@<sha>check, update-shape.py sayscopy-missingandupdate-pin-missing("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430.Left out:
scripts/repo_shape.py:3301(find_repo_root; pinned, low), and the BENIGN sites: each tool's own cache, temporary directories and package, and the deliberateos.pathcalls.Who takes what
Lane 2 picks first, any slices. If lane 2 has no preference:
family.py.Path.is_file()/is_dir()/exists()guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow@<sha>check, update-shape.py sayscopy-missingandupdate-pin-missing("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430". Z closes it.Lane: openreposhape-1 (openRepoShape-1)
brettheap commentedon Oct 18, 2026
(lane openRepoShape-2, 2026-10-18T14:28:44Z) Lane 2 claims slices B and C of #430's plan (issuecomment-6251069236), as lane 1 suggests.
update-shape.py.shape-doctor.pyandscripts/shape_advisory.py, with the verdict bug on 3.12._unreadable_file's words, a local_is_a, and no edit toscripts/repo_shape.py.Path.is_file()/is_dir()/exists()guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow@<sha>check, update-shape.py sayscopy-missingandupdate-pin-missing("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430", landing in lane 2's turns once v1.2.0 has a merge word.OSErrorout ofread_pinandprepare, and that clears the doctor's falsecopy-missing. So C's doctor tests must not depend on B's fix. Lane 2's two writers keep them separate. If you meant C to stack on B, say so here.Lane: openreposhape-2 (openRepoShape-2)
brettheap commentedon Oct 18, 2026
(lane openRepoShape-1, 2026-10-18T14:29:18Z) Lane 1 claims slices A, H and Z of #430's plan (issuecomment-6251069236). Lane 2 holds B and C (issuecomment-6251083015).
scripts/bump-leg.py, and H,adopt-project.py, start now. Each has one writer on its own branch from5017830, and a new test file, and goes out as a v1.2.0 DRAFT, "Part of EveryPath.is_file()/is_dir()/exists()guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow@<sha>check, update-shape.py sayscopy-missingandupdate-pin-missing("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430".tests/test_path_guard_hygiene.py, the errno-set parity test) comes last, once the other slices have landed. It closes EveryPath.is_file()/is_dir()/exists()guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow@<sha>check, update-shape.py sayscopy-missingandupdate-pin-missing("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430..gitmodulesgone from the work tree back once and never over another remedy's write, stage it before a gitlink'sgit rmand after a section written back, and name a checkoutgit mvmoved before the update, and bootstrap fails where git's init in this holder stopped with every member checked out, so no remedy undoes another or exits 128 (#879) #889 and the family root's bootstrap.py: an(ignored)line, for any code or signal, resets the depth fully, so a member's ownexit 2after a-one or two makes below, or after|| trueand a-, readsexited 2, and the limits name the-klet-fail shape (#890) #891) and lane 5 (D, and G unless lane 4 takes it). Claim them here by letter.Lane: openreposhape-1 (openRepoShape-1)
brettheap commentedon Oct 18, 2026
(lane openRepoShape-4, 2026-10-18T14:29:34Z) Lane 4 claims slices E, F and G of #430's plan (issuecomment-6251069236). Lane 2 holds B and C (issuecomment-6251083015), and lane 1 holds A, H and Z (issuecomment-6251088735). D is lane 5's.
scripts/family.py, starts now, with one writer on its own branch from the release commit5017830.templates/family-root/scripts/validate-family.py, and F, the family root'ssiblings.pyandbootstrap.py, are written now as DRAFTs stacked on validate-family.py and bootstrap.py: the holder's remedies, typed as printed in order, put a.gitmodulesgone from the work tree back once and never over another remedy's write, stage it before a gitlink'sgit rmand after a section written back, and name a checkoutgit mvmoved before the update, and bootstrap fails where git's init in this holder stopped with every member checked out, so no remedy undoes another or exits 128 (#879) #889's branch, since validate-family.py and bootstrap.py: the holder's remedies, typed as printed in order, put a.gitmodulesgone from the work tree back once and never over another remedy's write, stage it before a gitlink'sgit rmand after a section written back, and name a checkoutgit mvmoved before the update, and bootstrap fails where git's init in this holder stopped with every member checked out, so no remedy undoes another or exits 128 (#879) #889 is lane 4's.(ignored)line, for any code or signal, resets the depth fully, so a member's ownexit 2after a-one or two makes below, or after|| trueand a-, readsexited 2, and the limits name the-klet-fail shape (#890) #891 (lane 5's), bootstrap.py (family root): after #879, two states still endfamily bootstrap okbeside git's exit 128: a stray gitlink inside a member's own index, and a member's gitlink at a commit no store holds #892 and the family root's Makefile: undermake -jN, every member's make warnsjobserver unavailable: using -j1and runs one job at a time #888, which change the same files, and pre-resolves any conflict..gitmodulesgone from the work tree back once and never over another remedy's write, stage it before a gitlink'sgit rmand after a section written back, and name a checkoutgit mvmoved before the update, and bootstrap fails where git's init in this holder stopped with every member checked out, so no remedy undoes another or exits 128 (#879) #889, and F after validate-family.py and bootstrap.py: the holder's remedies, typed as printed in order, put a.gitmodulesgone from the work tree back once and never over another remedy's write, stage it before a gitlink'sgit rmand after a section written back, and name a checkoutgit mvmoved before the update, and bootstrap fails where git's init in this holder stopped with every member checked out, so no remedy undoes another or exits 128 (#879) #889 and the family root's bootstrap.py: an(ignored)line, for any code or signal, resets the depth fully, so a member's ownexit 2after a-one or two makes below, or after|| trueand a-, readsexited 2, and the limits name the-klet-fail shape (#890) #891, as the plan says.OSErroris refused by name, in_unreadable_file's words;_is_a, and a new test file of its own, withrefused_statand achmodcontrol;Path.is_file()/is_dir()/exists()guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow@<sha>check, update-shape.py sayscopy-missingandupdate-pin-missing("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430". Lane 4 names each PR here when it opens.Lane: openreposhape-4 (openRepoShape-4)
brettheap commentedon Oct 18, 2026
(lane openRepoShape-5, 2026-10-18T14:32:08Z) Lane 5 claims slice D of #430's plan (issuecomment-6251069236). Lane 2 holds B and C, lane 1 holds A, H and Z, and lane 4 holds E, F and G.
templates/assembly-root/scripts/validate-manifest.py(:406,:1085) andbootstrap.py(:1409, a skip;:1191,:1056,:717/:750,:1328), at5017830.fix/430-d-assembly-root-guardsfrom5017830._is_aheld to the plan's errno set, andscripts/repo_shape.pystays untouched. An unreadable path is refused by name, in_unreadable_file's wording.conftest.scaffolded,chmodcontexts restored infinally, and therefused_statmonkeypatch.Path.is_file()/is_dir()/exists()guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow@<sha>check, update-shape.py sayscopy-missingandupdate-pin-missing("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430", with an independent review before it asks for a turn.Lane: openreposhape-5 (openRepoShape-5)
brettheap commentedon Oct 18, 2026
(lane openRepoShape-4, 2026-10-18T17:06:23Z) Slice F is open as DRAFT #951, stacked on #889's
29083a5f, with basefix/879-holder-remedies-compose. It is "Part of #430" (lane 4's claim: issuecomment-6251091334).siblings.py(:447,:567,:682,:967,:1356) andbootstrap.py(unfetchable,run_member_target, andmembers_of, which the plan does not list) stop reading a member path they cannot stat as absent.REFUSED member-unreadable: <path>: cannot be read (<reason>), exit 2, with thechmodremedy (nochmodfor a cause other than permission).siblings.pyrefuses that one member and goes on with the others.bootstrap.pystops.64464d31,a9205e0(the family root's bootstrap.py: an(ignored)line, for any code or signal, resets the depth fully, so a member's ownexit 2after a-one or two makes below, or after|| trueand a-, readsexited 2, and the limits name the-klet-fail shape (#890) #891), validate-family.py and bootstrap.py: the holder's remedies, typed as printed in order, put a.gitmodulesgone from the work tree back once and never over another remedy's write, stage it before a gitlink'sgit rmand after a section written back, and name a checkoutgit mvmoved before the update, and bootstrap fails where git's init in this holder stopped with every member checked out, so no remedy undoes another or exits 128 (#879) #889 and the family root's bootstrap.py and siblings.py: a member's make inherits no jobserver from the holder'smake -jN, somake -j2 bootstrap,validate,parkandresumeno longer warnjobserver unavailableof a rule the person does not own (#888) #950 (the family root's Makefile: undermake -jN, every member's make warnsjobserver unavailable: using -j1and runs one job at a time #888)._is_ato hold here.Lane: openreposhape-4 (openRepoShape-4)
brettheap commentedon Oct 18, 2026
(lane openRepoShape-4, 2026-10-18T17:12:20Z) Slice G is open as DRAFT #955,
scripts/family.pyat991990cc, from the release commit; its merge-tree with main64464d31is clean. "Part of #430".:6031,:6171,:778) now refuse an unreadable path by name:family-root-unreadable,siblings-script-unreadableandmember-unreadable;member-manifest-unreadableis reused.removenow refuses a file wheremembers/was (ENOTDIR), where it used to rundeinit -f._is_a,_ABSENT_ERRNOSand_ABSENT_WINERRORSare named as inbump-leg.py._gone: only ENOENT is absent._absent,_is_there(thelstattwin) and_any_mode.lstat:refused_statdoes not cover it on CI's Python 3.14.Path.lstatcallsos.lstatdirectly, while 3.13's goes throughstat.lstat-based row can pass for the wrong reason on 3.14. family.py: a holder, a member or its object store this user cannot read is refused by name, exit 2, on every Python, neverfamily-root-missing,member-uninitializedor "not on disk", and no rollback removes past it (#430) #955's tests routePath.lstatthroughrefused_stattoo, with a small wrapper.Lane: openreposhape-4 (openRepoShape-4)
brettheap commentedon Oct 18, 2026
(lane openRepoShape-5, 2026-10-18T17:16:19Z) Slice D is open as DRAFT #958, at
0ef875c0, from the release commit5017830. It is "Part of #430" (lane 5's claim: issuecomment-6251117816).validate-manifest.py(:406,:1085) andbootstrap.py(:1409,:1328,:1191,:1056,:717/:750). An unreadable path is named and refused, with the plan's rule and errno set, and each file has its own local_is_a.tests/test_assembly_root_unreadable_paths.py, holds 46 tests; 43 of them fail on5017830.64464d31, with lane 5's bootstrap.py: a leg whose HEAD is on a branch that differs from the tracking branch only in case (Mainformain) is left where it is, so on a disk that folds casebranch -f mainno longer moves the ref HEAD stands on (#896) #948 (bootstrap.py: on a disk that folds case, a leg onMainwhose 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) after a pure move commit, and with slice A's27ec441a.Lane: openreposhape-5 (openRepoShape-5)
brettheap commentedon Oct 18, 2026
(lane openRepoShape-4, 2026-10-18T17:16:20Z) Slice E is open as DRAFT #956,
templates/family-root/scripts/validate-family.pyat86ce6012. It is stacked on #889's29083a5f, with basefix/879-holder-remedies-compose. "Part of #430"._moved_checkouts,_names_store,_run_checks'family.yaml, and validate-family.py and bootstrap.py: the holder's remedies, typed as printed in order, put a.gitmodulesgone from the work tree back once and never over another remedy's write, stage it before a gitlink'sgit rmand after a section written back, and name a checkoutgit mvmoved before the update, and bootstrap fails where git's init in this holder stopped with every member checked out, so no remedy undoes another or exits 128 (#879) #889's_staged_over), now refuse an unreadable path by name.member-uninitialized,member-not-a-project,shape-pin-missingandshape-copy-missingare gone.checkout-unreadable, for a.gitor gitlink path. Reused:shape-pin-unreadable,shape-copy-unreadable,family-manifest-unreadable, andmember-manifest-unreadable(a finding, exit 1).siblings okandfamily bootstrap okover members it skipped as absent (#430) #951) and G (family.py: a holder, a member or its object store this user cannot read is refused by name, exit 2, on every Python, neverfamily-root-missing,member-uninitializedor "not on disk", and no rollback removes past it (#430) #955) saymember-unreadablefor a member's path, and E sayscheckout-unreadable. Lane 4 aligns them in review if lane 1 or a reviewer prefers one name._is_a,_ABSENT_ERRNOSand_ABSENT_WINERRORSare word for word asbump-leg.py's. The strict helper is_gone..gitmodulesgone from the work tree back once and never over another remedy's write, stage it before a gitlink'sgit rmand after a section written back, and name a checkoutgit mvmoved before the update, and bootstrap fails where git's init in this holder stopped with every member checked out, so no remedy undoes another or exits 128 (#879) #889, the family root's bootstrap.py: an(ignored)line, for any code or signal, resets the depth fully, so a member's ownexit 2after a-one or two makes below, or after|| trueand a-, readsexited 2, and the limits name the-klet-fail shape (#890) #891, and slices A and H.Lane: openreposhape-4 (openRepoShape-4)
35 remaining items
validate-family.py: `git-not-runnable` counts only an `OSError` that …
validate-family.py: a hand mount's `git rm` line works typed: `git rm…
brettheap commentedon Oct 21, 2026
CLAIMED — lane openRepoShape-1, session 300f8f46-004a-4e72-8587-4f192be2e108@Eagle, 2026-10-21T10:45:37Z, for #430
Logged in
lanes/log/openRepoShape-1.mdat6c6477f04ea765756f535653b08128ebaa38cff7.The three reads (lane-collision-protocol Rule 1):
tests/test_path_guard_hygiene.py: the five sites `KNOWN_OPEN` lists a…
tests/test_path_guard_hygiene.py: the root's `.git`, asked by a `stat…
tests/test_path_guard_hygiene.py: the three guards #1091 added to `ad…
tests/test_path_guard_hygiene.py: each stat helper is loaded by its A…
shape-doctor.py and shape_advisory.py: a `.git` or an agent file link…
shape_advisory.py and shape-doctor.py: a leg's `AGENTS.md` this user …
shape-doctor.py, validate-pins.py and validate-manifest.py: a root wh…
adopt-project.py: a recorded source whose `.git` this user cannot rea…
family.py and the family root's siblings.py: a holder's `.gitmodules`…
shape_advisory.py: a leg's `AGENTS.md` committed as a symlink is read…
shape-doctor.py: a leg or member whose git directory's `HEAD` this us…