Skip to content

shape-doctor.py's legs NEXT after #354 does not fit four edge states: a populated mount with no .git but its gitdir kept is sent to validate-pins.py under a sentence saying no update cures it, though the git line does; an unborn HEAD gets the gate, which says pins ok while the row stays red; an empty mount with no .gitmodules section gets a git line that exits 128; and an empty mount whose pin is not 40 hex gets no "run the doctor again" #414

Closed

Description

@brettheap

Lane: openreposhape-4 (openRepoShape-4)

Found by this lane's adversarial review of #276 (PR #354, head c30bfb2: its N3, N4, N8 and N9). PR #354's body carries all four as follow-ups at its head cb2a684. #354 is open. Verified at cb2a684. On main 3ae0109 the legs NEXT for all four states is the root's bootstrap line, the line #276 is about. Local bare remotes, an empty global git config, GIT_ALLOW_PROTOCOL=file, and a gh that exits 97 (only the machine row calls it). Not claimed: macOS and Windows, which were not run, and the members row, which takes the same rule (members_verdict) and was not run for these states.

What is wrong. check_legs (shape-doctor.py:1874 at cb2a684) chooses the NEXT by kind:

  • checkout (:1950, :1962-1968): an empty mount, or a HEAD off the pin where the gitlink is the pin. The NEXT is the git line, and a sentence that says what it does.
  • lockstep (:1946, :1954, :1958, and the else at :1969): everything else. The NEXT is validate-pins.py, under "no submodule update cures a leg whose gitlink or pin is missing or disagrees, or a mount git cannot read a HEAD in" (lockstep_next, :1779).
  • When both kinds are present, the checkout wins and "Run the doctor again" is added (rerun=bool(lockstep), :1985-1986).

Four states do not fit that rule:

U=<openRepoShape checkout>   # a clean checkout of PR #354's head cb2a684
# Runs in bash on Linux (GNU coreutils). Nothing leaves the machine: every repository is a local bare one under $R, git
# reads an empty global config, GIT_ALLOW_PROTOCOL=file makes any accidental https/ssh clone fail, and a `gh` that exits
# 97 stands first on PATH.
R=$(mktemp -d); mkdir $R/bin; printf '#!/bin/sh\nexit 97\n' > $R/bin/gh; chmod +x $R/bin/gh; touch $R/gitconfig
export PATH=$R/bin:$PATH GIT_CONFIG_GLOBAL=$R/gitconfig GIT_CONFIG_NOSYSTEM=1 GIT_ALLOW_PROTOCOL=file \
       GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t.invalid GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t.invalid
doctor() { python3 $U/shape-doctor.py --root "$1" --json > "$1.json"; }
show() { python3 -c '
import json, sys
r = [r for r in json.load(open(sys.argv[1]))["rows"] if r["id"] == sys.argv[2]][0]
line, _, why = (r["next"] or "").partition("   # ")
print("  " + r["id"] + ":", r["status"], "|", r["reason"])
print("  NEXT:", line); print("  its sentence:", why)
open(sys.argv[1] + ".next", "w").write(line)' "$1" "$2"; }
python3 $U/scaffold-project.py --org testorg --project Atlas --elected-by 'Test Human' --elected-on 2026-10-12 \
    --local-remote-dir $R/remotes --work-dir $R/work >/dev/null
pasted() {  # paste the NEXT from elsewhere; print the lines of its output that match $2, then its exit
  (cd $R && sh -c "$(cat $1.json.next)" > $R/paste.out 2>&1); local rc=$?
  grep -E "$2" $R/paste.out | cut -c1-110; echo "  pasted: exit $rc"; }
fresh() { git clone -q --recurse-submodules $R/remotes/Atlas.git $1; }
again() { doctor $1; python3 -c 'import json,sys; r=[r for r in json.load(open(sys.argv[1]))["rows"] if r["id"]=="legs"][0]; print("  the doctor again:", r["status"], "|", r["reason"])' $1.json; }
echo "== (1) a populated mount with no .git of its own, its gitdir kept"
C=$R/1; fresh $C; rm $C/spec/.git
doctor $C; show $C.json legs; pasted $C '^(REFUSED|Remediation)'
git -C $C submodule update -q --init --recursive --checkout -- spec; echo "  the line its sentence rules out, run instead: exit $?"; again $C
echo "== (2) an unborn HEAD in the leg"
C=$R/2; fresh $C; echo 'ref: refs/heads/never-made' > $C/.git/modules/spec/HEAD
doctor $C; show $C.json legs; pasted $C '^(pins ok|FINDING|REFUSED)'; again $C
echo "== (3) an empty mount whose .gitmodules section is gone"
C=$R/3; fresh $C; git -C $C submodule deinit -q -f spec; git -C $C config -f .gitmodules --remove-section submodule.spec
doctor $C; show $C.json legs; pasted $C '^fatal'
echo "== (4) an empty mount whose pin is not 40 hex"
C=$R/4; fresh $C; git -C $C submodule deinit -q -f spec; sed -i -E 's/^commit: .*/commit: "abc123"/' $C/contracts/spec-pin.yaml
doctor $C; show $C.json legs; pasted $C '^(fatal|error)'; again $C

Output at cb2a684:

== (1) a populated mount with no .git of its own, its gitdir kept
  legs: FINDING | spec: checked out at 0aef20a5a255, pinned at aa09c2250df4
  NEXT: python3 $R/1/scripts/validate-pins.py --root $R/1
  its sentence: no submodule update cures a leg whose gitlink or pin is missing or disagrees, or a mount git cannot read a HEAD in. validate-pins.py prints what it finds and names the repair where it has one
REFUSED pin-submodule-uninitialized: spec is not an initialized submodule checkout, so the pinned digest canno
Remediation: run `git submodule update --init --recursive`, then `python3 scripts/validate-pins.py`. If the PI
  pasted: exit 2
  the line its sentence rules out, run instead: exit 0
  the doctor again: ok | 2 leg(s) mounted and checked out at their pins
== (2) an unborn HEAD in the leg
  legs: FINDING | spec: populated, but git cannot read a HEAD in it
  NEXT: python3 $R/2/scripts/validate-pins.py --root $R/2
  its sentence: no submodule update cures a leg whose gitlink or pin is missing or disagrees, or a mount git cannot read a HEAD in. validate-pins.py prints what it finds and names the repair where it has one
pins ok
  pasted: exit 0
  the doctor again: FINDING | spec: populated, but git cannot read a HEAD in it
== (3) an empty mount whose .gitmodules section is gone
  legs: FINDING | spec: the gitlink is there and the directory is empty -- an unbootstrapped clone
  NEXT: git -C $R/3 submodule update --init --recursive --checkout -- spec
  its sentence: checks each leg out at the commit this repository records for it, and fetches a mount that is empty. Then make bootstrap puts each leg on its tracking branch, and leaves alone one whose branch is already ahead of the pin
fatal: No url found for submodule path 'spec' in .gitmodules
  pasted: exit 128
== (4) an empty mount whose pin is not 40 hex
  legs: FINDING | spec: the gitlink is there and the directory is empty -- an unbootstrapped clone
  NEXT: git -C $R/4 submodule update --init --recursive --checkout -- spec
  its sentence: checks each leg out at the commit this repository records for it, and fetches a mount that is empty. Then make bootstrap puts each leg on its tracking branch, and leaves alone one whose branch is already ahead of the pin
  pasted: exit 0
  the doctor again: FINDING | spec: contracts/spec-pin.yaml carries no 40-hex commit to compare HEAD against
state what the row hands over what pasting it does
(1) populated, no .git in the mount, .git/modules/spec kept the gate, under a sentence saying no update cures it exit 2, pin-submodule-uninitialized, and the gate's own Remediation: says to run git submodule update --init --recursive. That line, run instead, exits 0 and the row is ok. The row's reason quotes the ROOT's HEAD as the leg's (0aef20a5a255 above is the root's), because git walks up out of the mount.
(2) an unborn HEAD in the leg the gate pins ok, exit 0. The row stays red, and nothing the row names changes it.
(3) an empty mount whose .gitmodules section is gone the git line fatal: No url found for submodule path 'spec' in .gitmodules, exit 128
(4) an empty mount whose pin is not 40 hex the git line, and no "Run the doctor again" exit 0. The re-run is red on the pin, which the sentence did not say was coming.

Why.

  • (1): check_legs routes on whether the mount has its own .git, but submodule update routes on whether the GITDIR exists. With the gitdir kept, the update re-links the mount and cures it, which the sentence rules out.
  • (2): an unborn HEAD is not a lockstep fact at all. The gitlink and the pin agree, so the gate has nothing to report.
  • (3): the checkout kind never asks whether git can find the leg's URL.
  • (4): rerun is computed from OTHER legs only. A leg routed to the checkout kind can carry a lockstep fact of its own (a pin that is not 40 hex), and the empty mount hides it until the next run.

What it should do. For each state, the NEXT either cures the row or is honest that it does not. The decisions are the author's:

  1. (1) Route a populated mount whose .git/modules/<path> exists to the git line, or correct the sentence to "unless its gitdir is still there". Separately, read no HEAD for a mount with no .git of its own, rather than the root's. That is the shape-doctor.py: the legs row's NEXT (and the members row's) runs <root>/scripts/bootstrap.py without asking whether the root has it, so a root that lost it pastes python3: can't open file, exit 2, and for an empty leg mount the legs NEXT would not cure the row even with the file there (bootstrap.py only prints Run git submodule update --init spec) #276 writer's out-of-scope item 1.
  2. (2) No line cures an unborn HEAD without choosing a commit for the person. So either NEXT None with a sentence that names the state, or a checkout of the gitlink in the leg (git -C <leg> checkout --detach <gitlink>), which moves no ref.
  3. (3) Ask for the URL (git config -f .gitmodules submodule.<name>.url) before choosing the git line. With none, route to the gate, or to restoring .gitmodules from HEAD when HEAD has the section.
  4. (4) Compute rerun per leg, so that a checkout-kind leg with its own lockstep fact gets the sentence too.

How to test. Each state in tests/test_doctor_legs_members_next.py, pasted and the doctor run again. Each NEXT either turns the row ok or is None with a sentence that names the state. For (4), the sentence says the run must be repeated.

Verified at PR #354's head cb2a684 (shape-doctor.py sha256 82a5ac78…).

Related: #276 / PR #354, #277 (the sentences move to why; in progress in this lane), #413, #415, and the #276 review's out-of-scope 3 (validate-pins.py reports a leg whose gitdir is gone as pin-commit-unresolvable, a misleading diagnosis).

Activity

  1. brettheap commented on Oct 14, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-4, session 11c6c0ef-495a-4a05-94cf-811b7b0a1012@Eagle, 2026-10-14T17:16:08Z, for #414

    Logged in lanes/log/openRepoShape-4.md at 42ebffbc1a58683798745fa77228d1db8beadf4d.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#414:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#414"`, kept where the row or its body names `#414`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `414` as a whole token:
       none
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions