Repository navigation
validate-pins.py and validate-family.py: a leg's or member's git-directory HEAD that is a FIFO, a socket or a device, or a symlink to one, is refused by name before git is asked, where a FIFO left the gate with no answer at all (#1290) - #1319
brettheap wants to merge 15 commits into
Conversation
… under `.git/modules/<leg>` or in a `.git` directory, is refused `pin-submodule-unreadable` by name, where it ended `pin-commit-unresolvable` (#1222) `_refuse_an_unreadable_git_dir` asked the `HEAD` of a leg's git directory only by `stat`, which a `HEAD` at mode 000 passes, so git, which could not read it, ended the run in `pin-commit-unresolvable` under THE LOCKSTEP RULE. A `HEAD` that is there is now opened as `_gitfile_opens` opens a gitfile: only where it is a regular file, and an open that finds nothing is a git directory with no `HEAD`, left to git. One this user cannot open is named, `.git/modules/spec/HEAD` or `spec/.git/HEAD`, with the leg checkout's remedy. The gate half of #1222; the doctor half waits for #1200. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… or whose git directory (its own `.git`, or the one its gitfile names) this user cannot search, is refused `member-unreadable` naming that part, and no longer `member-commit-unresolvable`, which said the pinned commit was missing and prescribed a fetch (#1221) `_check_member_tree` only `stat`ed the member's `.git` before running `git rev-parse` in it. A gitfile at mode 000 stats; a `.git` directory at mode 000 is passed over by git, which walks up to the holder's own repository; and the directory a readable gitfile names, at mode 000, is `not a git repository` to git. Each ended `member-commit-unresolvable`. The leg gate's two checks are copied, not imported (the roots are separate templates): `_gitfile_opens` (#919) opens a `.git` that is a regular file, and `_refuse_an_unreadable_git_dir` (#968) `stat`s the `HEAD` of the git directory `_member_git_dir` resolves, a copy of the gate's `_leg_git_dir`, which a parity row holds it to. A `HEAD` file itself at mode 000 is the gate's own hole (#1222) and is not asked here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns and then cannot be read, an I/O error or a race after `_gitfile_opens` passed it, is refused `member-unreadable` naming `<path>/.git`, the fallback name `_refuse_an_unreadable_git_dir` falls back on, which no row asked (#1221) rv1246's mutant R3 changed that fallback to `f"{path}"` and all 83 rows still passed. One in-process row refuses `Path.read_bytes` for the member's `.git` with EIO after the open succeeds and expects `members/IRRS/.git: cannot be read (Input/output error)` with the other-reason cure. With R3 applied the row fails; on main's bytes of validate-family.py it fails too. No code in validate-family.py changes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…annot open, under `.git/modules/<path>` or in a `.git` directory, is refused `member-unreadable` by name, where it ended `member-commit-unresolvable` with a fetch (#1256) A `HEAD` at mode 000 stats, so #1221's `stat` of it passed, and git, which could not read it, ended the run in `member-commit-unresolvable`, "the pinned commit ... is not in that member's object store", with a fetch that cannot help. `_refuse_an_unreadable_git_dir` now opens a `HEAD` that is there through `_gitfile_opens`, as validate-pins.py opens a leg's since #1222 (#1239): only a regular file is opened, an open that finds nothing is left to git, and a refused open is `member-unreadable` naming `.git/modules/<path>/HEAD`, `<path>/.git/HEAD`, or the whole path of a linked worktree's `HEAD` outside the holder, with the member checkout's remedy. A git directory with no `HEAD` stays git's. The copied helpers stay word for word with validate-pins.py's: `_gitfile_opens` and `_member_git_dir` (`_leg_git_dir`) compare equal by `ast.unparse` with docstrings dropped. Rows in tests/test_validate_family_unreadable.py: mode rows for a gitfile member, a `.git`-directory member and a linked worktree (skipped under root and on Windows), in-process stand-ins for each cause, a race that finds no `HEAD`, a directory or FIFO `HEAD` never opened, a NUL name left to git, and a parity row holding the line to the gate's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l3-1256-member-head-unreadable (restack) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… l3-1256-member-head-unreadable (restack) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ctory `HEAD` that is a FIFO, a socket or a device, or a symlink to one, is refused by name before git is asked, where a FIFO left the gate with no answer at all (#1290) #1222's and #1256's guard opens a `HEAD` only where it is a regular file, since the open of a FIFO waits for a writer; but git's own open of it, in the pinned commit's `git rev-parse`, waited, and `git_out` has no timeout, so the assembly gate and the family holder's gate gave no answer (RV1239 on #1239, W1256 on #1263). A `HEAD` that `stat` finds neither a file nor a directory is now `pin-submodule-unreadable` or `member-unreadable`, `<HEAD>: cannot be read (a FIFO, not a file)`, with the remedy for a cause that is no permission. A socket, or a symlink to a FIFO or a device, which git does not wait on but cannot read as a `HEAD`, ended the run in `*-commit-unresolvable` and is named the same way. The assembly root's own `.git/HEAD`, asked by the same function (#1146), is `git-repo-unreadable`. Choice (a) of #1290, not a timeout in `git_out`: measured, a timed-out call is a traceback (exit 1) or, made a `git-failed` refusal, `*-commit-unresolvable` again, and `scripts/repo_shape.py` is a shape copy a root may hold older than its gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideImplementationRead the two gates’ matching
TestsReview the assembly-gate cases together: bounded FIFO runs, unopened special-file checks, the root repository’s
Review the family-gate cases and parity checks together: they cover member git-directory shapes, bounded FIFO runs, refusal naming, and agreement with the assembly gate.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
(lane openRepoShape-3, 2026-11-10T17:54:45Z) RV1290: READY AFTER FIXES at be62b70 I reviewed this PR's own range, The three choices1. WHERE: (a) is right, and it settles what #1290's "To settle" asks.
2. WHICH KINDS: refusing every kind that
3. BEYOND THE ISSUE: covering the root's own
FindingsMUST: none. SHOULD 1 (body): the Merges section is stale. Main has moved twice since W1290 measured, to
>
SHOULD 2 (body): the root's socket and symlink-to-FIFO change is not stated.
NIT 1 (test text): the comment says git waits on a symlink to a FIFO, which it does not (measured). At NIT 2 (test text): the bounded rows' docstrings say the same thing.
NIT 3 (code docstring): NIT 4 (body, optional): "Of the probe's 54 lines" leaves out the two NIT 5 (follow-up, lane 3's call): two hangs remain, as the body says. A FIFO MeasuredGit 2.43.0, Python 3.12.3, uid 1000. Every run used a stub Before and after, with my own probe.
Leg's or member's git-directory
Other states:
Every changed output is what #1290 asks. Each cell of the PR body's own table matches my measurement. Rows. I selected the PR's 28 new and changed rows with
My mutants, M5 to M8. Each was applied with an exact-once text replacement, then the file was restored and checked with
Gates. One pytest at a time,
Body.
CI at 17:52Z: SonarCloud, Not measured, and paths
Lane 3: RV1290 ran every gate itself, so none is left. Lane 3 takes SHOULD 1, SHOULD 2 and NIT 4 into the body, word for word. Not taken, and why:
|
|
(lane openRepoShape-3, 2026-11-10T17:55:45Z) READY at
It added an "Independent review" section too. Not taken:
|
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 4 days and 19 hours by commenting |



What changed
A v1.2.2 fix for #1290, filed by lane 3 on Brett Heap's word "file follow-ups 5, 10 and 15" (2026-11-09) from RV1239's review of #1239 (issuecomment-6385532185) and W1256's "Out of scope" in #1263. It lands after the v1.2.1 tag and after #1263, which it is stacked on, once reviewed and green, in the order lane 3 posts for v1.2.2.
What a person sees. A leg's or a member's git directory whose
HEADis a FIFO made the assembly gate (templates/assembly-root/scripts/validate-pins.py) and the family holder's gate (templates/family-root/scripts/validate-family.py) give no answer at all: #1222's and #1256's guard does not open a FIFO, but git's own open ofHEAD, in the pinned commit'sgit rev-parse, waits for a writer, andgit_outhas no timeout. Now each gate refuses it by name, exit 2, before git is asked, in the words it already gives aHEADthis user cannot open:with the remedy for a cause that is no permission ("put a regular file this user can open back in its place, or take away one nobody wants"), not
chmod.A socket, a device, or a symlink to a FIFO, a socket or a device, which git does not wait on but cannot read as a
HEAD, ended the run inpin-commit-unresolvableormember-commit-unresolvable("the pinned commit is not in that ... object store"); each is named the same way now,(a socket, not a file),(a symlink to a FIFO, not a file). The assembly root's own.git/HEAD, which the same function asks (#1146), isgit-repo-unreadablethe same way: a FIFO there gave no answer, and a socket or a symlink to a FIFO there endedpin-gitlink-absent("this repository does not record it as a submodule"), about a root that records it. A regularHEAD, one at 000, a directory, a symlink that leads nowhere and noHEADanswer as they did.The two choices #1290 left open, settled by measurement
Where: (a), a by-name refusal in both gates, not (b), a timeout in
git_out, nor (c). Measured with the probe below, at #1263's head1962f749, withscripts/repo_shape.py'sgit_outchanged in the scaffolded root's own copy (the gate'srepo_shapeis the root's copy):HEADHEADtimeout=5ingit_out'ssubprocess.run, nothing elsesubprocess.TimeoutExpired: Command '['git', 'rev-parse', '--verify', '--quiet', '<commit>^{commit}']' timed out after 5 secondsgit-failedRefusalREFUSED pin-commit-unresolvable: spec: the pinned commit <commit> is not in that submodule's object store (git rev-parse ... timed out after 5 s)REFUSED member-commit-unresolvable: members/IRRS: the pinned commit <commit> is not in that member's object store (...)HEADfile at mode 000 passes the gate'sstatof HEAD and endspin-commit-unresolvable, and the doctor reads the leg DRIFTED, where both should name the file this user cannot open #1222 and validate-family.py: a member whose git directory'sHEADfile this user cannot open is refused by name,member-unreadable, where it is calledmember-commit-unresolvableand told to fetch #1256 took away: the pinned commit is missing, with the lockstep remedy or a fetch. Neither gate's_git_runsnor anyexcept Refusalaround a git call could tell a timeout from a missing commit without a change at every caller.scripts/repo_shape.py.git_outlives inscripts/repo_shape.py, a shape copy each root pins on its own, and the gate says a root's copy may be older than itself (validate-pins.py:644,:666and:1531at1962f749, the issue's:651at8cf6477fe). A timeout there reaches no root whose copy predates it, and such a root's gate keeps hanging. (a) lives in the gate file itself and imports nothing new fromrepo_shape, so it holds beside any copy.git_outis shared by every tool that imports it,tree_digest'sls-tree -rover a whole leg among them, and v1.2.2 adds no flag to say how long is too long.configin the leg's or member's git directory still gives no answer in either gate (measured below). It is written in the code and under "Out of scope".A link: covered, with every kind that is no file and no directory. The guard asks
stat, which follows a link, so the refusal covers aHEADthatstatfinds neither a regular file nor a directory: a FIFO, a socket, a device, and a symlink to any of them. Measured at1962f749:HEADwithreadlinkand takes it only where it namesrefs/. A gitfile leg's git saidfatal: not a git repository: .../.git/modules/spec, and a leg with its own.gitdirectory was passed over: git walked up to the root's repository (git rev-parse --verify --quiet HEADin the leg exited 0 there)./dev/null; a device node needs root) each endedpin-commit-unresolvable/member-commit-unresolvable, which says the pinned commit is missing. Each is the same fact as the FIFO, aHEADthat no git can read as one, and each is now named the same way.HEADis a FIFO hangs the assembly gate and the family holder's gate with no answer, becausegit_outhas no timeout and neither gate refuses aHEADthat is no regular file (RV1239 on #1239, W1256 on #1263; follows #1222 and #1256) #1290 asks: a regularHEAD, aHEADat 000 (validate-pins.py and shape-doctor.py: a leg's git-directoryHEADfile at mode 000 passes the gate'sstatof HEAD and endspin-commit-unresolvable, and the doctor reads the leg DRIFTED, where both should name the file this user cannot open #1222, validate-family.py: a member whose git directory'sHEADfile this user cannot open is refused by name,member-unreadable, where it is calledmember-commit-unresolvableand told to fetch #1256), a directory, a symlink that leads nowhere, and noHEAD. A symlink to a regular file is a file tostat, so it is left to git too, as before.The parity #1263 holds stands. The family gate's line for each of these
HEADs equals the assembly gate's (the parity row below), and the family gate's helper is a copy of the assembly gate's, not an import, as #1221 and #1256 copied theirs.Measured, before and after
The probe (
W1290-scratch/probe_1290.py, a pytest file loaded with-p conftestso it uses the suite's ownprojectandholderfixtures) scaffolds each gate from the tree under test and runs the root's ownscripts/validate-pins.pyor the holder'sscripts/validate-family.pyas a subprocess in a session of its own, bounded at 25 s, killing the whole session (git included) at the bound. Git 2.43.0, Python 3.12.3, uid 1000, offline (stubgh,GIT_SSH_COMMAND=false,LANES_LANEunset). Before is #1263's head1962f749; after is this head. Each cell is the exit and the first line;<commit>and the temporary paths are shortened. Both shapes (a gitfile leg or member, under.git/modules/, and one with its own.gitdirectory) gave the same answer in every row but where noted.HEADof the leg's or member's git directorypin-submodule-unreadable: .git/modules/spec/HEAD: cannot be read (a FIFO, not a file)(own.git: names<path>/.git/HEAD)member-unreadable: .git/modules/members/IRRS/HEAD: cannot be read (a FIFO, not a file)(own.git: names<path>/.git/HEAD)pin-commit-unresolvablepin-submodule-unreadable: .git/modules/spec/HEAD: cannot be read (a symlink to a FIFO, not a file)(own.git: names<path>/.git/HEAD)member-commit-unresolvablemember-unreadable: .git/modules/members/IRRS/HEAD: cannot be read (a symlink to a FIFO, not a file)(own.git: names<path>/.git/HEAD)pin-commit-unresolvablepin-submodule-unreadable: .git/modules/spec/HEAD: cannot be read (a socket, not a file)(own.git: names<path>/.git/HEAD)member-commit-unresolvablemember-unreadable: .git/modules/members/IRRS/HEAD: cannot be read (a socket, not a file)(own.git: names<path>/.git/HEAD)/dev/null)pin-commit-unresolvablepin-submodule-unreadable: .git/modules/spec/HEAD: cannot be read (a symlink to a device, not a file)(own.git: names<path>/.git/HEAD)member-commit-unresolvablemember-unreadable: .git/modules/members/IRRS/HEAD: cannot be read (a symlink to a device, not a file)(own.git: names<path>/.git/HEAD)pin-commit-unresolvablepin-commit-unresolvablemember-commit-unresolvablemember-commit-unresolvablepin-commit-unresolvablepin-commit-unresolvablemember-commit-unresolvablemember-commit-unresolvablepin-commit-unresolvablepin-commit-unresolvablemember-commit-unresolvablemember-commit-unresolvableHEAD) the git directory'sconfiga FIFO, gitfile shape.git/HEADa FIFOgit-repo-unreadable: .git/HEAD: cannot be read (a FIFO, not a file).git/HEADa socket, or a symlink to a FIFOpin-gitlink-absentgit-repo-unreadable: .git/HEAD: cannot be read (a socket, not a file)or(a symlink to a FIFO, not a file)gitlinks-unreadablegitlinks-unreadable(the holder's ownHEAD, #1172's)Where the before answer is
*-commit-unresolvable, git saidfatal: not a git repository: .../.git/modules/specfor a gitfile leg, and for a leg with its own.gitdirectory it walked up to the root's repository (git rev-parse --verify --quiet HEADin the leg exited 0 there) andrev-parseof the pinned commit exited 1 with nothing. The two(b)rows are in the table under "The two choices".Byte for byte. Of the probe's 54 lines, with the temporary paths and the per-scaffold commit masked, 19 changed: the four kinds above in both shapes and both gates (16), the assembly root's own FIFO
HEAD(1), and the two emulated (b) runs, which now never reach git (2). The other 35 are identical before and after: every control state, the FIFOconfig, the holder's own FIFOHEAD, and the baregit rev-parselines. RV1290 re-measured it with 66 states, comparing the exit and the whole stdout and stderr with commits and temporary paths masked: 27 changed and 39 are identical, every change one named above.No git was left waiting: each probe and row that timed out killed its session, and
psshowed nogit rev-parseof this run after its bound.Rows
Both gates' rows are in the modules that already hold that area's rows, beside #1222's and #1256's own
HEADrows:tests/test_validate_pins_spelled_and_named.py(the leg gate's "#1222: a leg git directory'sHEAD" section) andtests/test_validate_family_unreadable.py(#1256's section and its parity rows). The family module importsbounded,NOT_HEADSandFIFO_HEADSfrom the leg gate's module (functions and parameters, no fixture), so both run in one pytest.boundedruns a gate asrun_scriptdoes, but in a session of its own for 30 s at most; a run that has not ended is killed with its whole session (the git waiting on the FIFO among it) and fails the row. A hang fails its row, never the run, and no git is left waiting.test_a_fifo_head_is_refused_by_name_within_a_bound(FIFO and symlink to a FIFO x gitfile leg and own.gitleg, the whole stderr and exit 2, nothing written);test_a_head_git_cannot_read_is_refused_by_name_unopened(FIFO, socket, symlink to a FIFO, symlink to a device x both shapes, in-process, the guard asked alone so git is never started, open forbidden);test_the_root_s_own_fifo_head_is_refused_by_name_within_a_bound(git-repo-unreadable,.git/HEAD); and the controltest_a_head_that_is_a_directory_is_left_to_git.test_a_member_fifo_head_is_refused_by_name_within_a_bound(FIFO and symlink to a FIFO x gitfile member and own.gitmember,assert_refused: the line, exit 2, no traceback, the cure for a cause that is no permission, and nomember-commit-unresolvable); the PARITY rowtest_a_member_head_git_cannot_read_is_named_as_the_gate_names_a_leg_s(the four kinds x both shapes: the family gate's line equals the leg gate's, word for word); and the controltest_a_member_head_that_is_a_directory_is_left_to_git(both gates leave it to git).HEADfile at mode 000 passes the gate'sstatof HEAD and endspin-commit-unresolvable, and the doctor reads the leg DRIFTED, where both should name the file this user cannot open #1222'stest_a_head_that_is_no_regular_file_is_not_openedand validate-family.py: a member whose git directory'sHEADfile this user cannot open is refused by name,member-unreadable, where it is calledmember-commit-unresolvableand told to fetch #1256'stest_a_member_head_that_is_no_regular_file_is_not_openedeach held that a FIFOHEADis passed to git (is None). That is the state validate-pins.py, validate-family.py and scripts/repo_shape.py: a git directory whoseHEADis a FIFO hangs the assembly gate and the family holder's gate with no answer, becausegit_outhas no timeout and neither gate refuses aHEADthat is no regular file (RV1239 on #1239, W1256 on #1263; follows #1222 and #1256) #1290 changes, so each keeps its directory case as the control (renamed..._is_a_directory_is_left_to_git), and its FIFO case moves to the rows above, which still forbid the open.HEADthis user cannot open, under.git/modules/<path>or in a.gitdirectory, is refusedmember-unreadableby name, where it endedmember-commit-unresolvablewith a fetch (#1256) #1263's head (its two gates swapped in, these rows kept, then restored and checked withcmp):25 failed, 3 passed, 212 deselected in 164.88s. The 3 that pass are the three directory controls, which hold on both. Five failed asFailed: validate-pins.py gave no answer in 30 sorvalidate-family.py ...(the four plain-FIFO rows and the root's), and the symlink-to-FIFO rows failed on*-commit-unresolvable, as the probe measured.28 passed, 212 deselected in 12.81s. The rows skip where there is noos.mkfifo(the leg module'sPOSIX_NODES).Mutants
Each applied to the committed file, the 28 rows run, the file restored from
HEADand checked withcmp(allok, tree clean after).validate-pins.py: the new_a_head_git_cannot_read(head)call dropped21 failed, 7 passed(every new leg row but the directory control, the root row, and every parity row; the family gate's own rows pass)validate-family.py: the same call dropped12 failed, 16 passed(every member bounded row and every parity row)validate-pins.py: a directoryHEADrefused too (S_ISREGalone)3 failed, 25 passed(the three directory controls)validate-family.py: a symlink not named as one (link = "")6 failed, 22 passed(the symlink rows, bounded and parity)Gates
One pytest at a time,
-rs, with a stubghfirst on PATH,GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/null, git-filter-repo 2.47.0 on PATH andLANES_LANEunset, on the committed headbe62b704with a clean tree:tests/test_validate_pins_spelled_and_named.py tests/test_validate_family_unreadable.py tests/test_validate_pins_without_git.py tests/test_family.py tests/test_family_add_refusals.py tests/test_family_head_lost_gitmodules.py:535 passed in 429.85s (0:07:09).pin-submodule-unreadable,member-unreadable,git-repo-unreadable), with their fixture sources:tests/test_validate_pins_named_and_spelled.py tests/test_validate_pins_without_git.py tests/test_family_gitmodules_unreadable.py tests/test_family_hand_mount_git_rm_lines.py tests/test_family_py_unreadable.py tests/test_family_siblings_bootstrap_unreadable.py tests/test_family_siblings_placement.py tests/test_validate_family_git_not_runnable.py tests/test_family.py tests/test_family_add_refusals.py tests/test_family_head_lost_gitmodules.py tests/test_pinned_root_git_unreadable.py tests/test_root_git_unreadable.py:1113 passed in 772.84s (0:12:52).tests/test_repo_hygiene.py tests/test_cli_reference.py tests/test_agents_md_codes.py tests/test_git_output_decoding.py tests/test_stdout_encoding.py tests/test_path_guard_hygiene.py tests/test_no_auto_maintenance.py tests/test_update_add_codes_documented.py tests/test_pinned_copy_text.py tests/test_link_source_chain_defaults.py tests/test_template_text_placeholders.py:725 passed, 3 skipped in 226.98s (0:03:46). The skips are the host's:test_stdout_encoding.py:292("the default this is about is Windows' own") andtest_no_auto_maintenance.py:222x2 ("the session began with GIT_CONFIG_GLOBAL set", which the lane's gate environment sets to/dev/null).tests/test_shape_doctor.pyandtests/test_scaffold_leg_path_collisions.pywere not run:adopt-project.pyis not changed. The two root modules take fixtures fromtests/test_shape_doctor.pyand ran without it for that reason; neither changes a fixture.Merges
git merge-tree --write-tree, with main atf39c6707(and atdc9a0bcc, #1225) and every open PR that touches these four files (gh pr list --state open): #1263 itself and #1310 (headdaaa5ad8, which holds6b9a4ece;validate-family.py).be62b7041962f749alonef39c67079265d65d6d487f1cdc9a0bcc1d3f8d0a1b0353b1daaa5ad849b19ec5e43b74a66b9a4ece954a17f3ce4391b5This head merged with main
f39c6707, then with #1310daaa5ad8: clean, tree112620e7. No conflict, the parent's or this PR's. (RV1290's measure.)Stacked
Stacked on #1263 (head
1962f7498171f6cf481bbc169cb915c42700ef05). Its first commits are #1263's; this PR's own change is1962f749..be62b704. Review that range. It lands after #1263, by a restack onto its squash.Out of scope
configin a leg's or member's git directory still gives no answer in either gate (the probe:TIMEOUT, both gates). The guard asksHEADalone, as validate-pins.py and shape-doctor.py: a leg's git-directoryHEADfile at mode 000 passes the gate'sstatof HEAD and endspin-commit-unresolvable, and the doctor reads the leg DRIFTED, where both should name the file this user cannot open #1222 and validate-family.py: a member whose git directory'sHEADfile this user cannot open is refused by name,member-unreadable, where it is calledmember-commit-unresolvableand told to fetch #1256 asked it; a by-name check of every file git reads at discovery, or a bound on git itself (validate-pins.py, validate-family.py and scripts/repo_shape.py: a git directory whoseHEADis a FIFO hangs the assembly gate and the family holder's gate with no answer, becausegit_outhas no timeout and neither gate refuses aHEADthat is no regular file (RV1239 on #1239, W1256 on #1263; follows #1222 and #1256) #1290's (b), with the costs above), would be a change of its own. It is written in the code beside the new check.HEAD(templates/family-root/scripts/validate-family.py's_refuse_an_unreadable_root, whichstats it and opens nothing): a FIFO there still gives no answer (the probe:TIMEOUT). It is 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 area, as validate-pins.py, validate-family.py and scripts/repo_shape.py: a git directory whoseHEADis a FIFO hangs the assembly gate and the family holder's gate with no answer, becausegit_outhas no timeout and neither gate refuses aHEADthat is no regular file (RV1239 on #1239, W1256 on #1263; follows #1222 and #1256) #1290's "Not here" says. The assembly root's own.git/HEADis asked by the shared_refuse_an_unreadable_git_dir(shape-doctor.py: a shape root whose own.gitis a link into a folder this user cannot search reads DRIFTED, its legs as having no gitlink, and both of its validators crash with PermissionError #1146), so it is refused here.HEADthat does not namerefs/: git does not take it as aHEAD(the probe:fatal: not a git repositoryfor a gitfile leg, and a walk up to the root's repository for an own.gitleg), so the run still ends*-commit-unresolvable.statfinds a file there, and validate-pins.py, validate-family.py and scripts/repo_shape.py: a git directory whoseHEADis a FIFO hangs the assembly gate and the family holder's gate with no answer, becausegit_outhas no timeout and neither gate refuses aHEADthat is no regular file (RV1239 on #1239, W1256 on #1263; follows #1222 and #1256) #1290 does not ask it.objects/orconfigat 000, the doctor'smembersrow (shape-doctor.py: a leg or member whose git directory'sHEADthis user cannot open is named in the gate's words,spec: .git/modules/spec/HEAD: cannot be read (<reason>)orspec/.git/HEAD: ..., so thelegsandmembersrows read INVALID with the gate's line and never DRIFTED, andplacementnever calls the leg "not a git repository" (#1222) #1248), and the doctor's half of validate-pins.py: a leg git directory'sHEADthis user cannot open, under.git/modules/<leg>or in a.gitdirectory, is refusedpin-submodule-unreadableby name, where it endedpin-commit-unresolvable(#1222) #1239 stay where validate-pins.py, validate-family.py and scripts/repo_shape.py: a git directory whoseHEADis a FIFO hangs the assembly gate and the family holder's gate with no answer, becausegit_outhas no timeout and neither gate refuses aHEADthat is no regular file (RV1239 on #1239, W1256 on #1263; follows #1222 and #1256) #1290's "Not here" puts them.Independent review
RV1290, lane 3's independent opus review (issuecomment-6774950982), reads READY AFTER FIXES at
be62b704. The fixes are to the body only, and it found no MUST. It judged all three choices right:git_out;statfinds neither a file nor a directory;HEAD, with the family gate's copy held by a parity row.Its own probe of 66 states found every change to be what #1290 asks. Its mutants M5 to M8 were all killed. Its gates passed:
tests/test_shape_doctor.py: 1559 passed, 3 skipped.Taken here: SHOULD 1 (the merges, at main
f39c6707), SHOULD 2 (the root's socket and symlink-to-FIFO change) and NIT 4.Not taken:
configand the holder's own FIFOHEAD. A follow-up candidate for Brett Heap.Closes #1290
Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code
Summary by Sourcery
Reject non-file, non-directory Git
HEADobjects by name before Git runs so validation exits with actionable refusals instead of hanging.New Features:
HEADis a FIFO, socket, device, or symlink to one before invoking Git, with clear diagnostic names and remediation guidance.Bug Fixes:
HEAD.HEADhandling to the assembly root's repository and preserve parity between assembly and family validation gates.Enhancements:
HEADstates while distinguishing special filesystem objects in refusal messages.Tests:
HEADfilesystem objects across both validation gates.