Repository navigation
shape_advisory.py and shape-doctor.py: a leg's AGENTS.md this user cannot read leaves whether it is a leg unknown, so the way in offers neither command and points at what is here, and the other way in and the Triad row are silent (#1110) - #1159
brettheap wants to merge 6 commits into
Conversation
…cannot read leaves whether the folder is a leg unknown, never no leg's file, so `the way in` offers neither command and points at `what is here`, which names it, and `the other way in` and the Triad row are silent (#1110) #1110's twelfth site, found by r1144: `shape_advisory._leg_by_agents_md` read any exception as "no leg's file" (`except Exception: return None`). Over a clone of acme/Orbit whose AGENTS.md is the spec or the code leg's, linked into a folder at 000 or itself at 000, #1144's head offered `adopt-project.py plan ... --project Orbit` in `the way in`, `setup.sh` in `the other way in`, and the Triad advice, on 3.12 and on a 3.14-like shim. - `_leg_by_agents_md` raises a refused `stat` or read (#430's rule: only ENOENT, ENOTDIR, EBADF and ELOOP, and Windows' 21, 123 and 1921, are nothing there). Bytes that are not UTF-8 still declare no leg; anything else ends in `read()`'s net. - `_read` answers `not-answered`, with `AGENTS.md: cannot be read (<reason>)` in `why` and the OSError in the new `Reading.refused`, unless the folder's name is a leg's, which still answers. The `.git` case carries `refused` too. - `leg_clone` raises `refused`; `the way in` (`way_in_unread_row`, shared with the `.git` row) offers neither command, and `the other way in` is silent. - `what is here` reads AGENTS.md through (`read_through`), so a file at 000 is named there with the cure. tests/test_doctor_unsearchable_links.py: 20 new rows (3 mode, 17 in process), and `leg_clone` over a refused `.git` now raises in #1144's row. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR replaces broad suppression of leg metadata read failures with explicit refusal handling, carries that uncertainty through the advisory, and updates the doctor to provide a safe, actionable report without offering adoption or scaffolding for a possibly existing Triad leg; extensive tests validate filesystem variants and existing behavior. Sequence diagram for unreadable leg metadata advisorysequenceDiagram
participant Doctor as shape-doctor.py
participant Advisory as shape_advisory.read()
participant Tree as Tree.text()
participant FS as Filesystem
Doctor->>Advisory: read(root, shape)
Advisory->>Tree: _leg_by_agents_md()
Tree->>FS: read AGENTS.md
FS-->>Tree: PermissionError
Tree-->>Advisory: OSError
Advisory->>Advisory: _leg_by_name(names, policy)
Advisory-->>Doctor: Reading(NOT_ANSWERED, refused)
Doctor->>Doctor: leg_clone(ctx)
Doctor-->>Doctor: raise reading.refused
Doctor-->>Doctor: way_in_unread_row(AGENTS_MD, ...)
Note over Doctor: No adoption, scaffolding, or Triad row
Flow diagram for unreadable AGENTS.md doctor reportflowchart TD
A[Repository contains AGENTS.md] --> B{AGENTS.md readable?}
B -->|Yes| C[Determine leg from metadata or name]
C --> D[Offer leg-specific or repository way in]
B -->|No| E{Leg name identifies it?}
E -->|Yes| F[Report known leg clone]
E -->|No| G[Return NOT_ANSWERED with refused OSError]
G --> H[what is here names AGENTS.md and cure]
G --> I[the way in offers no NEXT command]
G --> J[the other way in is silent]
G --> K[Triad advisory row is omitted]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-1, 2026-10-22T15:30:29Z) Independent review r1159 of #1159 at Review of PR #1159 (head 0f9aff0, base #1144's head 89486f6)Verdict: NOT READY. The code is correct as I measured it, and nothing is a MUST. Two SHOULDs are left: one PR-body edit and one missing control row in the tests. There are also five NITs. I stopped at your LAST STEP word. The mutant that was already running (M18b) finished and restored itself, and I started nothing after it. FindingsSHOULD-1: PR body, "Not in this PR", lines 181-183
SHOULD-2:
NIT-1: PR body, Tests, line 110
NIT-2:
NIT-3: PR body, Tests, lines 118 and 120-125. Base figures are stale and incomplete.
NIT-4:
NIT-5:
Observations, not findings
MutantsEach ran alone in a clone, on 3.12, over
Test counts
Every base failure is on the row's own assertion. The control rows (leg by name, Doctor-report comparison256 report files, paths normalised.
Greps and static checks
Not done (outside this review's list): a re-run of the writer's 18 mutants, and the Files
Lane: openreposhape-1 (openRepoShape-1) |
…ere` to reading through a regular `AGENTS.md` alone: a directory or a named pipe of that name is listed and not read, the pipe's doctor run under a timeout, and a refused `Makefile` is listed like any other name (#1110) The review of #1159 (r1) found two mutants of `shape-doctor.py` alive against all 45 rows: - M3, `read_through` reading any kind of file (`is_a(path, lambda _m: True)`): over a repository whose `AGENTS.md` is a directory, `what is here` said `AGENTS.md: cannot be read (Is a directory)`, and over one whose `AGENTS.md` is a named pipe the doctor never returned (SHOULD-2); - M14, `what is here` reading through every name it lists (NIT-4). New rows, each a control that holds on #1144's head as here: - `test_an_agents_md_that_is_no_regular_file_is_listed_and_not_read` [directory, named-pipe]: the doctor run as a person runs it, given `DOCTOR_SECONDS` (120) by `doctor_within`, so a run that opens the pipe fails the row instead of hanging the suite. `what is here` says `a git repository, an AGENTS.md`, nothing says `cannot be read`, and the clone is offered both ways in. The pipe row skips where `os.mkfifo` is absent, and nothing opens the pipe in the test's own process. - `test_what_is_here_reads_through_agents_md_alone`: a leg's clone whose `Makefile` read is refused, in process: `what is here` says `a git repository, an AGENTS.md, a Makefile`, and no row says `cannot be read`. `offered_both_ways_in` now reads its rows through `offers_both_ways_in`, which a whole run's rows (with `machine`) can be given too. Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lder named as a leg is still that leg, as it was before #1159 and as it is where the disk's file is refused, so `adopt-project.py plan` and `check` print the leg's note over it again (#1110) The review of #1159 (r1, NIT-2) measured that a `Refusal` raised while reading a commit's `AGENTS.md` (`Tree.text`, `cat-file blob`) no longer fell back to the leg's name. #1144's head read it as no leg's file and then asked the name, so a folder named `<P>-spec` was `leg-clone`; #1159's head sent the `Refusal` straight to `read()`'s net, `not-answered`, and `adopt-project.py` stopped printing the leg's note there. `_read` now catches a `Refusal` from `_leg_by_agents_md` as it catches a refused `OSError`: the folder's name answers where it is a leg's, and with none the `Refusal` goes on to `read()`'s net, `not-answered`, as on #1159's head. This is the review's mutant M17. `refused` stays None: no `OSError` was refused. New row, a control that holds on #1144's head as here: `test_a_leg_by_its_name_is_one_while_git_cannot_give_its_agents_md`, a committed clone named `Orbit-spec` whose `cat-file blob` of `AGENTS.md` raises `Refusal`: `leg-clone`, by name, the spec leg. On #1159's head `0f9aff05` it fails with `not-answered`. Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot read keeps `origin_org` in its `--json` detail, as the leg's own row and the policy row keep it with no command, where it said None while `origin` was read (#1110) The review of #1159 (r1, NIT-5) found that the `AGENTS.md` row gave `origin_org: null` while the `naming` row of the same run read `origin`, and that the docstring's reason, "None because no command is offered with them", did not hold: the leg's own row keeps `origin_org` with no command, and so does the policy row (`way_in_policy_row`). `way_in_unread_row` now takes `org`: None for a `.git` that cannot be read, whose `origin` is not read either (unchanged), and `origin_org(root)` for the `AGENTS.md` row, read only where `carries_git` answered and `leg_clone` raised. `suggested_project` stays None, since no `--project` is offered, and the docstring says so. `no_leg_way_in` now expects `origin_org: "acme"` (ORIGIN is `acme/Orbit`), so the 7 rows that use it pin the value: with the doctor of `34ed0a95` they fail, 7 failed and 42 passed. Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts name where git cannot give its `AGENTS.md` no longer asks `Reading.refused`, which #1144's `Reading` does not have, so it is the control on #1144's head its docstring says it is (#1110) Measured with the file laid over #1144's head `89486f69`, the row's state, `by` and role checks pass there, and only `assert reading.refused is None` failed, with `'Reading' object has no attribute 'refused'`. `refused` is set only together with `not-answered`, so the line asked nothing of a `leg-clone` reading. Without it the row passes on `89486f69` and on this head, and still fails on `0f9aff05`, whose `Refusal` skips the name (`not-answered`, at the state check). Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-1, 2026-10-22T20:18:30Z) Independent re-check r1159b of #1159 at Verdict: READY. f1159's round answers every finding from r1159 (SHOULD-1, SHOULD-2 and NITs 1 to 5). My measurements show no new MUST or SHOULD. Two optional NITs are below. One thing outside the PR: GitHub's Linux The live PR still matches what we expected: head The r1 findings
New problemsNone at MUST or SHOULD.
MeasurementsEverything ran on 3.12 in the probe environment, one basetemp per call, at most 2 calls at once.
Mutants I re-ran at
These all match the body's table. The body's gate rows I did not re-run: the PR body: title 245 characters; no close, fix or resolve form before an issue number; no State: my checkout FilesAll in /workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1159b:
Lane: openreposhape-1 (openRepoShape-1) |
… row names its sibling row on one line, so a grep for the row's name finds the reference (#1110) Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
(lane openRepoShape-1, 2026-10-24T14:43:48Z) d1159, lane 1's independent delta checker (not the writer): READY carries from
The full delta checkd1159 reportPart 1 is complete: READY carries from Part 2 is partial. #1164's head has not moved since the review that names it, and I re-measured nothing on it. I stopped at lane 1's LAST STEP. No runs of mine are live. Part 1: #1159 (
|
| commit | parents | what it changes | only what its message says? |
|---|---|---|---|
66e68f948 "tests/test_doctor_unsearchable_links.py: the docstring of the control row names its sibling row on one line, so a grep for the row's name finds the reference (#1110)" |
0ca336110 (one parent, not a merge) |
One file, +4/-3. It rewraps the docstring of test_a_leg_by_its_name_is_one_while_git_cannot_give_its_agents_md, so the backticked row name sits on one line. This is r1159b's NIT-B. |
Yes for the change. See NIT-2 for the grep wording in the message. |
Commands and output:
git log --format='%h %p %s' 0ca33611..66e68f94prints that one commit.git rev-list --countgives 1 and--mergesgives 0.git diff --name-only 0ca33611 66e68f94printstests/test_doctor_unsearchable_links.pyalone.- Every other file is byte-identical. A hash of
git ls-tree -r, with that file excluded, isf8d8b6d9858e3836c015e86ffb7d380cc6fc6290at both commits. Soshape-doctor.pyandscripts/shape_advisory.pydid not change. - The AST of the old and new test file, with docstrings blanked, is equal:
AST equal with docstrings blanked: True. Exactly one docstring differs, and the old and new texts are equal after whitespace normalisation and healing the_ be_break. Only one string constant differs, and the file is one line longer (1089 to 1090). - Nothing in the repo cites a line number of the file (
grepfortest_doctor_unsearchable_links.py:[0-9]finds nothing). - No added line is non-ASCII or over 80 characters.
The delta is not a behaviour change, so the brief's "at least 3 mutants of the changed lines" does not strictly apply. I ran 4 spot-check mutants anyway, to show the rows still bite at 66e68f94.
Test runs
Run in the probe environment: stub gh, bogus token, empty GH_CONFIG_DIR, GIT_CONFIG_GLOBAL=/dev/null, GIT_SSH_COMMAND=false, env -u LANES_LANE, one basetemp per call, at most 2 calls at once. I ran df -h /workspace and df -i / and counted pytest processes before each call; the minimum free was 16G.
| call | result |
|---|---|
links file at 66e68f94, 3.12 |
49 passed (149.8 s) |
links file at 66e68f94, 3.14-like shim |
49 passed (147.6 s) |
tests/test_shape_advisory.py, tests/test_path_guard_hygiene.py, tests/test_no_auto_maintenance.py at 66e68f94 |
154 passed, 2 skipped (280 s). The collect counts are 47 + 49 + 60 = 156. |
base 89486f69 plus head's test file, 3.12 |
17 failed, 32 passed |
base 89486f69 plus head's test file, shim |
17 failed, 32 passed |
Base comparison:
- The failing set on 3.12 equals r1159b's set at
0ca33611(diffempty), and the shim's set equals the 3.12 set. - The base file has 25 rows and head has 49, so "gains 24 rows" is right.
- The shim was loaded:
shim loaded: True. On a file inside a mode-000 folder, plain 3.12is_fileraisesPermissionErrorand the shim answers False.
GitHub checks on 66e68f94: tests, tests-git-bash, package and SonarCloud all completed with success.
Mutants
Spot-check, not required by the delta. Run on 3.12 in a clone at 66e68f94. Each file was restored in a finally, and the blob matched HEAD's afterwards.
| mutant | file | rows failed | blob restored |
|---|---|---|---|
M3: read_through reads any kind of file |
shape-doctor.py |
2 failed, 47 passed (directory row and named-pipe row) | True |
N2: except Refusal disabled |
scripts/shape_advisory.py |
1 failed (..._by_its_name_is_one_while_git_cannot_give_its_agents_md), 48 passed |
True |
N5: the AGENTS.md way-in row drops origin_org |
shape-doctor.py |
7 failed, 42 passed | True |
M14: what is here reads through every name |
shape-doctor.py |
1 failed (test_what_is_here_reads_through_agents_md_alone), 48 passed |
True |
All 4 were killed, with the same counts as the body's table. After M3 no stray doctor process was left over the FIFO.
The body
- Closing form. Exactly one: line 1,
Part of #1110.closingIssuesReferencesis[]. A regex for close, fix or resolve before an issue number finds nothing. The only other#Nforms are "Filed as shape_advisory.py: a committedAGENTS.mdthat is a symlink is read, inadopt-project.py's commit reading, as the text of its target's path, so a leg cloned with itsAGENTS.mdas a link is told it is a single repository #1163" (line 210) and mentions of scripts/shape_advisory.pyrepository_namesand shape-doctor.pyorigin_urlandorigin_namereadgit remote get-url originthroughgit_outand catchRefusalandOSErroronly, so an origin URL that is not UTF-8 makesreadanswernot-answered(a single repository loses the Triad advisory, a leg clone its pointer to the assembly root) and endsshape-doctor.py --rootin a UnicodeDecodeError traceback, exit 1, where an ASCII origin gives NOT A SHAPE ROOT, exit 2 #385, 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 and shape-doctor.py and shape_advisory.py: a.gitor an agent file linked into a folder this user cannot search is said once inwhat is hereoragent files,<name>: cannot be read (<reason>), never absent; a.gitoffers no way in (#1110) #1144. There is no@. - Stacking. Right. The base is
fix/1110-doctor-advisory-unsearchable-git, shape-doctor.py and shape_advisory.py: a.gitor an agent file linked into a folder this user cannot search is said once inwhat is hereoragent files,<name>: cannot be read (<reason>), never absent; a.gitoffers no way in (#1110) #1144's head is still89486f69, and shape-doctor.py and shape_advisory.py: a.gitor an agent file linked into a folder this user cannot search is said once inwhat is hereoragent files,<name>: cannot be read (<reason>), never absent; a.gitoffers no way in (#1110) #1144 is open. - Live PR. Head is
66e68f94, still a draft, 6 commits, title 245 characters. The body is byte-equal to the copy I read at the start. - Claims about the new commit. There are none. See NIT-1.
Findings
No MUST and no SHOULD.
NIT-1: the PR body does not name the delta.
- PR body lines 16, 67, 125, 151 and 189 label head, "Head
0ca33611" and the measurements "at0ca33611". The body names neither66e68f94nor a docstring-only commit ('66e68f94' in bodyis False, and so is "docstring"). - All of those measurements still hold. I re-measured the counts that matter at
66e68f94(49/49, 17/32, 47 + 49). - Fix: add one sentence, for example "
66e68f94changes one docstring intests/test_doctor_unsearchable_links.pyand nothing else (r1159b's NIT-B); every figure above carries from0ca33611."
NIT-2: the commit message overstates the grep claim.
tests/test_doctor_unsearchable_links.py:1046now reads`..._by_its_name_is_one_while_its_agents_md_cannot_be_read`). It stays abbreviated with..._.grep -n "test_a_leg_by_its_name_is_one_while_its_agents_md_cannot_be_read"finds only thedefat line 885. That is r1159b's own example string. A grep for the suffixby_its_name_is_one_while_its_agents_md_cannot_be_readfinds both lines 885 and 1046.- r1159b's fix, "break the line before the backticks", was applied as asked, so the NIT is answered as asked.
- Optional fix: spell the full
test_a_leg_by_...name in the docstring.
NIT-3 (observation): non-ASCII in the body.
- The body holds one
'(U+2019) besides the emoji. It is in the Sourcery-generated block ("doctor's"), not in lane text. - r1159b said the emoji was the only non-ASCII character. If the squash message is cut from that block, replace it.
Not covered in Part 1
- I did not re-run the
test_shape_doctorshards,test_doctor_next_is_a_command, the 7-file list, the doc trio, or the doctor-report comparison. Their inputs are unchanged: all non-test files are byte-identical, and the test file is AST-equal apart from one docstring. They carry from0ca33611. - I did not check landing mechanics. shape_advisory.py and shape-doctor.py: a leg's
AGENTS.mdthis user cannot read leaves whether it is a leg unknown, sothe way inoffers neither command and points atwhat is here, andthe other way inand the Triad row are silent (#1110) #1159 still lands after shape-doctor.py and shape_advisory.py: a.gitor an agent file linked into a folder this user cannot search is said once inwhat is hereoragent files,<name>: cannot be read (<reason>), never absent; a.gitoffers no way in (#1110) #1144, after the v1.2.0 tag, on a later word. A stacked PR after its parent's squash needs the three-merge procedure.
Part 2: #1164 (read-only checks, no test or mutant run)
- The review names the current head. issuecomment-6371395973 (brettheap, 2026-10-23T17:59:47Z) says "READY at
74096eb4". The PR head is74096eb4783384ec46b366f9d6098426a5aa9f28, the full sha the brief gives. - No commits after the named head.
gh pr viewlists 7 commits, from4352ac2fdto74096eb47. The last was committed 2026-10-23T07:41:46Z, before the review at 17:59Z. There are no GitHub reviews and no review comments. So there is no delta to check, and the READY carries by identity. - GitHub checks on
74096eb4: SonarCloud,package,testsandtests-git-bashall completed with success. - The body took r1164c's two NITs.
- Line 113 now says "PR bootstrap.py, validate-family.py and siblings.py: a root or holder whose
.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172 for bootstrap.py, and the family root's validate-family.py, bootstrap.py and siblings.py: a root whose own.gitthis user cannot read is a PermissionError traceback on 3.12, ornot-a-git-repowhereexists()answers False, not refused by name #1148, now at5aa0f116", with themerge-treequoted at74096eb47. bootstrap.py, validate-family.py and siblings.py: a root or holder whose.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172's current head is5aa0f116d, so that matches. - Lines 84 and 90 now say "(4 at
74096eb47, with thefamily.yamlrows)".
- Line 113 now says "PR bootstrap.py, validate-family.py and siblings.py: a root or holder whose
- Closing form. Line 1 is
Part of #1110, andclosingIssuesReferencesis[]. I read the body for other close, fix or resolve forms and saw none, but I did not run a regex on it. The title is 249 characters.
Flags, not findings
- Body line 113 says "this head merges with
b75babee3cleanly". shape-doctor.py, validate-pins.py and validate-manifest.py: a root whose own.gitthis user cannot read is named.git: cannot be read (<reason>), so the doctor never reads it as DRIFTED, and both pinned validators refuse it, exit 2 (#1146) #1147's head is now569ea0565. It wasb75babeewhen r1164c measured, and I did not check when it moved. I did not rungit merge-treeagainst569ea0565. - family.py and the family root's siblings.py: a holder's
.gitmodulesthis user cannot read, as through a link into a folder it cannot search, isgitmodules-unreadable, never no section forremove's rollback or no url formake siblings(#1110) #1164's base038dafba9is 20 commits behind main (comparesaysdiverged, ahead 7, behind 20), andmergeable_statereadsunknown. I did not test a merge with the current main. - Suggested check for lane 1:
git merge-tree --write-tree --name-only 74096eb47 569ea0565, and the same against main.
Not covered in Part 2: I re-ran no tests, mutants, gate rows or pin measurements. I did not diff #1164 against anything, because there is no later commit.
State
- Review checkout
/workspace/projects/openRepoShape-worktrees/l1-d1159: detached at66e68f94,status --shortempty. - Scratch clones
mandm2are at66e68f94and clean. Clonebis at base89486f69and carries the head test file overlaid, plus an untrackedtests/test_zzbase_links.py(I run norm). Both are intentional. - No process of mine is running.
Files
All in /workspace/projects/openRepoShape-worktrees/_lane1-scratch/d1159/:
checkpoint.mdlogs/(the run logs:links-head-312.log,links-head-shim.log,links-base-312.log,links-base-shim.log,gate-adv-hyg-nam.log,mut-M3.log,mut-N2.log,mut-N5.log,mut-M14.log)probes/mut.pyshim/sitecustomize.pypenv.sh,run.shbody.md,body1164.md,pr-meta.json,pr-view.json,pr1164.json
Lane: openreposhape-1 (openRepoShape-1)
Lane: openreposhape-1 (openRepoShape-1)



Part of #1110: its twelfth site,
scripts/shape_advisory.py_leg_by_agents_md.Stacked on #1144. Its base is
fix/1110-doctor-advisory-unsearchable-git(#1144's head89486f69), and it lands after #1144.Outside v1.2.0. Brett Heap ruled at 2026-10-21T10:32:04Z: "leave the follow-ups out of v1.2.0". This PR lands after the v1.2.0 tag, on a later word.
What
On #1144's head,
_leg_by_agents_mdread any exception as "no leg's file" (except Exception: return None). The fixtures are clones ofacme/OrbitwhoseAGENTS.mdis the spec leg's or the code leg's file, rendered from its template, in two forms:link: the working-tree file is a link into a folder at mode 000;mode: the file itself is at mode 000.Over either form, the doctor offered
adopt-project.py plan ... --project Orbitinthe way inandsetup.shinthe other way in, and its Triad advisory said "This is a single repository". This held on 3.12 and on a 3.14-like shim, wherePath.is_file,existsand the rest answer False to anyOSError.Base
89486f69against head0ca33611:shape-doctor.py --root <fixture>/Atlas, 32 runs, made at0f9aff05. Re-run at0ca33611, the 16 head reports are byte-identical to0f9aff05's, paths normalised. In--json, the locked fixtures'the way inrow now keepsorigin_org: "acme"where0f9aff05gavenull(4 of 4: spec and code,linkandmode), and nothing else differs.link, lockedwhat is herenamesAGENTS.md: cannot be read (Permission denied).the way ingives NEXTadopt-project.py plan,the other way ingives NEXTsetup.sh, and anadvisory Triadrow says "This is a single repository". Exit 2.what is herenames it, with the cure.the way ingives the reason below and no NEXT. There is nothe other way inrow and no Triad row. Exit 2.mode, lockedwhat is heresaysa git repository, an AGENTS.md. The adopt, scaffold and Triad rows are as above. Exit 2.link, locked.linkandmode)The new
the way inreason:its AGENTS.md cannot be read, so it is not known whether this is a clone of a Triad's leg, which is not adopted or scaffolded on its own, and neither way in is offered until it can be:what is heresays how.At
0f9aff05, every report on 3.12 is byte-identical to its report under the shim (32 of 32). With paths normalised, the code leg's locked reports equal the spec leg's (16 of 16). The readable ones differ only in the leg's name.In process (
leg_cloneandshape_advisory.read(), 3.12 and shim):AGENTS.mdread()isadviseandleg_cloneis Falseread()isnot-answered, withwhyAGENTS.md: cannot be read (Permission denied), so whether this is a leg clone is not known here, and the advisory says nothing, andleg_cloneraisesPermissionErrorleg-clone, Trueleg-clone, Trueadopt-project.py planandcheck: 64 runs at0f9aff05, equal on base and head, 3.12 and shim. Where git cannot give a commit'sAGENTS.md, a folder named as a leg still gets the leg's note, as on base; with no such name,planandcheckprint no advisory, where base printed the advice. No fixture meets this. Adopt reads the commit'sAGENTS.md, never the disk's, so a locked working-tree file changes nothing there.adopt-project.pyis not changed.The whole doctor report: spec leg,
link, locked, 3.12<standard>is the checkout and<fixture>the fixture's folder.Base
89486f69:Head
0ca33611(and0f9aff05, byte for byte):For
mode, locked, base differs only inwhat is here, which saysa git repository, an AGENTS.mdwith no NEXT or WHY. Head's report is the same as forlink.Why
_leg_by_agents_mdfollows 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's rule: anOSErrorwhose errno says nothing is there answers None, as before. Those errnos are ENOENT, ENOTDIR, EBADF and ELOOP, and Windows' 21, 123 and 1921. Any otherOSErroris raised.repository_namesand shape-doctor.pyorigin_urlandorigin_namereadgit remote get-url originthroughgit_outand catchRefusalandOSErroronly, so an origin URL that is not UTF-8 makesreadanswernot-answered(a single repository loses the Triad advisory, a leg clone its pointer to the assembly root) and endsshape-doctor.py --rootin a UnicodeDecodeError traceback, exit 1, where an ASCII origin gives NOT A SHAPE ROOT, exit 2 #385's rule against salvaging text that is not UTF-8; see Known limits.Refusal) ends inread()'s net,not-answered.AGENTS.md,_readturns to_leg_by_namefirst, so a folder named as a leg is stillleg-clone. Without a name, the answer isnot-answered, and the refusedOSErroris kept in the newReading.refused.AGENTS.md._readcatches theRefusalofcat-file blob(inadopt-project.py's reading of a commit) as it catches a refusedOSError. A folder named as a leg isleg-clone, as on base, soplanandcheckprint the leg's note. With no such name, theRefusalgoes on toread()'s net,not-answered.refusedstays None, since noOSErrorwas refused..gitnot-answeredcarriesrefusedtoo.refusedis not inas_dict, soas_dictkeeps its keys.leg_cloneraisesreading.refused.check_the_way_inasksleg_cloneright aftercarries_git. On anOSErrorit givesway_in_unread_row("AGENTS.md", ...), a helper shared with the.gitrow, whose words are unchanged.--json, theAGENTS.mdrow keepsorigin_org, as the leg's own row and the policy row keep it with no command. The.gitrow's stays None, since itsoriginis not read.suggested_projectis None in both, since no--projectis offered.check_the_other_way_inis silent.what is herereads the file through. The newread_throughreads a regularAGENTS.md, so a file at mode 000, whosestatsucceeds, is named there with the cure. It does not raise a read that finds nothing.AGENTS.mdis listed and not read. A read of the directory is refused (Is a directory), and the open of a pipe nobody writes to blocks the doctor for ever.AGENTS.mdalone. Every other name is asked, as before._nothing_there(exc)is now shared with_is_a;_cannot_be_read(name, exc)spells bothwhylines from thestrerror, or the exception's type name where there is none.Tests
tests/test_doctor_unsearchable_links.pygains 24 rows, for 49 in all:doctor_within), over anAGENTS.mdthat is a directory and one that is a named pipe. A run that opens the pipe fails the row instead of hanging the suite. The pipe row skips whereos.mkfifois absent, and nothing opens the pipe in the test's own process;Two of #1144's rows change:
test_the_doctor_reads_the_advisory_as_silentnow expectsPermissionErrorfromleg_cloneover a refused.git, where it said False;test_the_advisory_names_a_git_it_cannot_reach_and_says_nothingalso checkstype(reading.refused) is type(error).89486f69(new file laid over)0ca33611Every base failure is on the row's own assertion:
way-in-scaffoldortriad-advisoryrow (7);DID NOT RAISE(4);'Reading' object has no attribute 'refused'(3);'advise' == 'not-answered'(2);what is heresayingan AGENTS.mdfor a file at mode 000 where there is no repository,'an AGENTS.md' == 'AGENTS.md: cannot be read (Permission denied)'(1).The new controls pass on base:
AGENTS.mdis refused and where git cannot give the commit's;AGENTS.mdthat is not there (dangling, gone before it is read, loop);AGENTS.mdthat is a directory or a named pipe: listed, not read;Makefilewhose read is refused: listed like any other name;Two of this round's changes are pinned by rows that fail without them:
AGENTS.mdfails on0f9aff05(not-answered);origin_orgback to None, the 7 rows that useno_leg_way_infail (7 failed, 42 passed).Mutants: 31, 28 killed. The 3 survivors are those the review accepted as equivalent. Each ran alone, in a clone, over
tests/test_doctor_unsearchable_links.pyon 3.12, and the file's bytes matched its HEAD blob after each one. N2, and M3 over its named-pipe row, ran at0ca33611. The rest ran ate356013b, which differs from0ca33611only by one assertion in N2's row, a row no other mutant fails.scripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyscripts/shape_advisory.pyshape-doctor.pyshape-doctor.pyscripts/shape_advisory.pyUnicodeDecodeErroris the onlyValueErrortext()can raiseshape-doctor.pyleg_cloneraises a.gitit cannot read ascarries_gitdoes, and is False where there is noneshape-doctor.pyscripts/shape_advisory.pyscripts/shape_advisory.py0f9aff05's_read)scripts/shape_advisory.pyshape-doctor.pyshape-doctor.pyA1-A10 and D1-D8 are the writer's first 18. M3-M19 are the review's (r1), and N2-N5b are this round's. M3 fails both of its rows: the directory row on its assertion, and the named-pipe row on its timeout,
Failed: the doctor did not end within 120 seconds over .../Atlas: something opened its AGENTS.md, after 126 s. The blocked doctor is killed, and no doctor process is left. A9 is caught only by #1144's two rows, which this change extends: no brand-new row catches it.The gate, call by call (3.12 unless named, the probe environment, one basetemp per call, at
0ca33611):tests/test_doctor_unsearchable_links.pytests/test_doctor_unsearchable_links.py, shimtests/test_shape_advisory.py,tests/test_path_guard_hygiene.pytests/test_shape_doctor.py, shard 1 of 4tests/test_shape_doctor.py, shard 2 of 4tests/test_shape_doctor.py, shard 3 of 4tests/test_shape_doctor.py, shard 4 of 4tests/test_doctor_next_is_a_command.py(namescheck_the_way_in)tests/test_adopt_single_repository_record.py,test_bom_reads.py,test_bootstrap_non_utf8_branch.py,test_doctor_gitmodules_not_utf8.py,test_doctor_unreadable_paths.py,test_doctor_validator_verdicts.py,test_repo_hygiene.py(the rest of the files that nameshape_advisory)Known limits
repository_namesand shape-doctor.pyorigin_urlandorigin_namereadgit remote get-url originthroughgit_outand catchRefusalandOSErroronly, so an origin URL that is not UTF-8 makesreadanswernot-answered(a single repository loses the Triad advisory, a leg clone its pointer to the assembly root) and endsshape-doctor.py --rootin a UnicodeDecodeError traceback, exit 1, where an ASCII origin gives NOT A SHAPE ROOT, exit 2 #385's rule. One example is a leg'sAGENTS.mdwhose opening is intact ahead of a byte in a legacy code page; another is UTF-16. Over such a file the doctor still offers adoption and the Triad advisory, on base and head alike. Decoding the opening leniently was rejected, because scripts/shape_advisory.pyrepository_namesand shape-doctor.pyorigin_urlandorigin_namereadgit remote get-url originthroughgit_outand catchRefusalandOSErroronly, so an origin URL that is not UTF-8 makesreadanswernot-answered(a single repository loses the Triad advisory, a leg clone its pointer to the assembly root) and endsshape-doctor.py --rootin a UnicodeDecodeError traceback, exit 1, where an ASCII origin gives NOT A SHAPE ROOT, exit 2 #385 forbids salvaging text that is not UTF-8. The rowstest_a_leg_agents_md_that_is_not_utf8_declares_no_leg[a-legacy-byte-after-the-opening]and[utf-16]hold this behaviour, and mutant A4 shows they catch a change to it.leg_clonenow raises over a refused.git, where shape-doctor.py and shape_advisory.py: a.gitor an agent file linked into a folder this user cannot search is said once inwhat is hereoragent files,<name>: cannot be read (<reason>), never absent; a.gitoffers no way in (#1110) #1144's row said False. Both callers askcarries_gitfirst, so no report changes.Not in this PR
AGENTS.md.adopt-project.py's commit reader reads it as its target's path, soplanandchecksay "This is a single repository" over a link to a leg's file, readable or not. Meanwhile the doctor, which reads the disk, calls the same checkout a leg. This is the same on base and head. Filed as shape_advisory.py: a committedAGENTS.mdthat is a symlink is read, inadopt-project.py's commit reading, as the text of its target's path, so a leg cloned with itsAGENTS.mdas a link is told it is a single repository #1163.Lane: openreposhape-1 (openRepoShape-1)
🤖 Generated with Claude Code
Summary by Sourcery
Treat unreadable leg metadata as an unknown repository shape and guide users to restore access before choosing an entry path.
New Features:
AGENTS.mdand report the result as unknown rather than treating it as a non-leg.Bug Fixes:
AGENTS.mdcannot be read.AGENTS.mdfiles with actionable guidance while preserving name-based leg detection and existing handling of absent or non-UTF-8 files.Enhancements:
what is hereread regularAGENTS.mdfiles through to identify read failures.Tests: