Repository navigation
shape-doctor.py's two naming rows read only the naming script's exit code, so a crash of the STANDARD's own validate-repository-naming.py is a FINDING about the project (INVALID (naming), exit 1, where #357 makes a crashed template CANNOT ANSWER), and a directory whose name is not UTF-8 gets naming FINDING Atl?as: UnicodeEncodeError: ..., the script's traceback quoted as a fact about the name #410
Description
Lane: openreposhape-4 (openRepoShape-4)
Found by this lane's planner for the doctor cluster, section 4, item 3: "check_naming ... and check_not_a_root_naming ... also read only the exit code: a crash of the standard's own naming script reads as a finding. R278-1 covers validator_row only." PR #357, which closes #278, adds crashed() for validator_row and leaves both naming rows as they are. Verified on main 3ae0109 and at #357's head 8ad856f, which print the same. 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. Case (1) needs a file name that is not UTF-8, which Windows does not have.
What is wrong. Both naming rows run the STANDARD's own scripts/validate-repository-naming.py, which is this checkout's and never the project's. Both turn any non-zero exit into a FINDING about the repository:
check_naming(shape-doctor.py:1256at 3ae0109) reports "the naming policy is not satisfied (exit N): ";check_not_a_root_naming(:3573) reports the quoted line as the reason.
Neither asks whether the script reached a verdict:
U=<openRepoShape checkout> # a clean checkout at 3ae0109
# 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
row() { python3 -c '
import json, sys
d = json.load(open(sys.argv[1]))
r = [r for r in d["rows"] if r["id"] == "naming"][0]
print(" naming:", r["status"], "|", r["reason"])
print(" verdict:", d["verdict"] + ", exit", d["exit"])' "$1"; }
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
git clone -q --recurse-submodules $R/remotes/Atlas.git $R/Atlas
echo "(1) the stock standard, a directory that is not a root and whose name is not UTF-8"
D=$(printf '%s/elsewhere/Atl\xffas' $R); mkdir -p "$D"
python3 $U/shape-doctor.py --root "$D" --json > $R/1.json; row $R/1.json
echo "(2) a checkout of the standard whose own naming script crashes, asked about a compliant project"
git clone -q $U $R/std
printf 'raise RuntimeError("boom")\n' > $R/std/scripts/validate-repository-naming.py
python3 $U/shape-doctor.py --root $R/Atlas --json > $R/2a.json; echo " the stock standard:"; row $R/2a.json
python3 $R/std/shape-doctor.py --root $R/Atlas --json > $R/2b.json; echo " the broken one:"; row $R/2b.jsonOutput at 3ae0109 (8ad856f prints the same):
(1) the stock standard, a directory that is not a root and whose name is not UTF-8
naming: FINDING | Atl?as: UnicodeEncodeError: 'utf-8' codec can't encode character '\udcff' in position 5: surrogates not allowed
verdict: NOT A SHAPE ROOT, exit 2
(2) a checkout of the standard whose own naming script crashes, asked about a compliant project
the stock standard:
naming: ok | every leg name classifies, and each declared role agrees with its form
verdict: COMPLIANT, exit 0
the broken one:
naming: FINDING | the naming policy is not satisfied (exit 1): RuntimeError: boom
verdict: INVALID (naming), exit 1
- (1) A name that is not UTF-8. The doctor hands the directory's name to the script (
--explain -- <name>), andrun_validatorsetsPYTHONIOENCODING=utf-8(:979). The script prints the name, and the UTF-8 encoder refuses the surrogate that stands for the byte\xff: a traceback, exit 1. The row quotes that traceback as a fact about the name. The verdict for a directory that is not a root isNOT A SHAPE ROOTwhatever this row says. What is wrong is the row: in--json,namingis a FINDING whose reason is an exception. - (2) A standard whose naming script crashes. The same compliant project reads
INVALID (naming), exit 1, a statement about the project caused by a fault in the checkout asking. For a crash of the standard's TEMPLATE validator, shape-doctor.py: a validator that crashed says it crashed instead of "reports a finding", and is CANNOT ANSWER when it is the standard's template, which a standard whose path holds thePYTHONPATHseparator no longer runs at all; a neutral product's digestvalidate-pins.pyskipped is anotenaming the product, from a child that can no longer reachghor a proxy; and a root whose manifest does not load and whosescripts/repo_shape.pyis gone is told to restore that file before its gate (#278, #297, #298) #357's rule isdetail.environment, thenCANNOT ANSWER, exit 3: "that copy is this checkout's, not the project's, so nothing here is a statement about the project". The naming script is always this checkout's.
The script's own crash on such a name is a separate defect of validate-repository-naming.py, named here for whoever fixes it. #385 is the doctor's own traceback on an origin URL that is not UTF-8, which is another read of another name.
Why. The naming rows were written when the exit code was the whole protocol: 0 classifies, 1 does not. That is the reading #278 reports in validator_row, and PR #357 replaces there. The naming rows call run_validator directly and never go through validator_row, so #357 does not reach them.
What it should do. A naming script that did not reach a verdict is never a FINDING about the repository. The decisions are the author's:
- The test. Reuse shape-doctor.py: a validator that crashed says it crashed instead of "reports a finding", and is CANNOT ANSWER when it is the standard's template, which a standard whose path holds the
PYTHONPATHseparator no longer runs at all; a neutral product's digestvalidate-pins.pyskipped is anotenaming the product, from a child that can no longer reachghor a proxy; and a root whose manifest does not load and whosescripts/repo_shape.pyis gone is told to restore that file before its gate (#278, #297, #298) #357'scrashed()once it has landed, in both rows. A negative exit, from a signal, belongs in the same test (shape-doctor.py, after #357'scrashed(), still reads a validator killed by a signal (a negative exit, -9 or -15) or one that left byos._exit(1)with nothing printed as one that "reports a finding", becausecrashed()needs a traceback line, so a root's own gate ended by the OOM killer or a timeout's SIGTERM is a FINDING about the project, and the standard's template killed the same way is not CANNOT ANSWER #447). - What a crash becomes. In an assembly root or a holder:
detail.environment, soCANNOT ANSWER, exit 3, with "crashed before reaching a verdict" and the quoted last line, as shape-doctor.py: a validator that crashed says it crashed instead of "reports a finding", and is CANNOT ANSWER when it is the standard's template, which a standard whose path holds thePYTHONPATHseparator no longer runs at all; a neutral product's digestvalidate-pins.pyskipped is anotenaming the product, from a child that can no longer reachghor a proxy; and a root whose manifest does not load and whosescripts/repo_shape.pyis gone is told to restore that file before its gate (#278, #297, #298) #357 does for a template. In a directory that is not a root, the verdict is fixed, so only the row's status and wording change. - The script itself. Whether this issue also makes
validate-repository-naming.pyprint a name it cannot encode (backslashreplace) or refuse it by name, or that goes in an issue of its own.
How to test. Beside #357's crash tests:
- A copy of the standard whose naming script raises, asked about a compliant project: the
namingrow carriesdetail.environmentand the verdict isCANNOT ANSWER, exit 3. - POSIX only (
WINDOWS_SKIP): a directory that is not a root, whose name holds\xff. Thenamingrow is not a FINDING quotingUnicodeEncodeError.
Verified at main 3ae0109 (shape-doctor.py sha256 f1731ff4…, scripts/validate-repository-naming.py 362efbbd…) and at #357's head 8ad856f (shape-doctor.py 9ceb97d7…).
Related: #278 / PR #357 (crashed() for validator_row), #447 (a validator killed by a signal), #408, #385 (the doctor's own crash on a non-UTF-8 origin), #104 (the naming row's -- before names), #338 (the naming script's --link-source).
Activity
brettheap commentedon Oct 14, 2026
v1.1.0 (triage of 2026-10-14): one pull request with #448, the doctor's reading of validator output, under one rule: a verdict is a printed FINDING or REFUSED, anything else is a crash, and WARNING and note lines become a
note. #447 and the pins row's dropped note lines (#460, comment 6136245436) can ride along (backlog).brettheap commentedon Oct 14, 2026
CLAIMED — lane openRepoShape-4, session 11c6c0ef-495a-4a05-94cf-811b7b0a1012@Eagle, 2026-10-14T16:51:29Z, for #410
Logged in
lanes/log/openRepoShape-4.mdatf1b127570ad607f4dc6e96c905faf9ea769ce2fa.The three reads (lane-collision-protocol Rule 1):
shape-doctor.py: a checkout whose own `contracts/repository-naming.ya…
Merge origin/main (66b230e) into l3-683-partial-clone
README.md, the handbook and the v1.1.0 notes: the doctor's exit 3 nam…