Repository navigation
adopt-project.py check takes no --local-remote-dir and resolves a relative submodule url from GitHub's remotes, while execute --local-remote-dir resolves it from the bare repositories it creates, so under that test path check refuses an adoption execute verifies and passes one execute refuses #417
Description
(lane openRepoShape-1, 2026-10-12T16:42:20Z)
Raised by lane openRepoShape-2's review of the #190 branch at b0c959e: its P3-1, in its comment on #190. PR #245 (#190) left it for a follow-up when it landed as 161c6c9: a draft fix "came to eleven places and 75 changed lines of adopt-project.py, before its tests and docs/cli.md", because every message that says where check resolves from would change. It is on main since #245. Before #245 (main b30595c) check resolved no submodule url, so check and execute agreed.
What is wrong. Since #245, check and execute resolve each relative submodule url the plan keeps in a leg twice, from the source's remote and from the leg's, and compare the two answers. check takes no --local-remote-dir, so it always takes the leg's remote to be https://github.com/<org>/<leg>.git. execute --local-remote-dir resolves again from the bare repositories it is about to create. Under that test path, check's verdict therefore says nothing about execute's, in either direction:
- Layout A is an adoption that works: the source's remote and the dependency sit beside the local legs.
checkexits 1 withFINDING plan-submodule-relative-url.executeverifies, and the assembly's recursive clone succeeds. - Layout B has the source's remote on GitHub and the legs local.
checksaysplan ok, with a NOTE.executerefuses before it creates any leg.
SHAPE=<openRepoShape checkout>
export GIT_AUTHOR_NAME=T GIT_AUTHOR_EMAIL=t@example.invalid GIT_COMMITTER_NAME=T GIT_COMMITTER_EMAIL=t@example.invalid
SUB="git -c protocol.file.allow=always"
for layout in A B; do
TMP=$(mktemp -d); mkdir "$TMP/remotes"
git init -q -b main "$TMP/remotes/dep" && (cd "$TMP/remotes/dep" && echo dep > value.txt && git add -A && git commit -qm dep)
git init -q -b main "$TMP/Thing" && cd "$TMP/Thing" || exit 1
mkdir src docs && printf 'print(1)\n' > src/app.py && printf '# Spec\n' > docs/spec.md
printf '# Thing\n' > README.md && git add -A && git commit -qm init
$SUB submodule add -q "$TMP/remotes/dep" vendor/lib
git config -f .gitmodules submodule.vendor/lib.url ../dep && git add .gitmodules && git commit -qm vendor
if [ $layout = A ]; then
git init -q --bare -b main "$TMP/remotes/Thing.git" && git remote add origin "$TMP/remotes/Thing.git"
else
git remote add origin git@github.com:testorg/Thing.git
fi
echo "== layout $layout: origin $(git remote get-url origin | sed "s#$TMP#<T>#"), url ../dep"
python3 "$SHAPE/adopt-project.py" plan --source "$TMP/Thing" --project Northwind --org testorg \
--elected-by 'T H' --elected-on 2026-09-02 --out "$TMP/plan.yaml" >/dev/null
sed -i 's/^ leg: null$/ leg: code\n resolution: "the vendored library is code"/' "$TMP/plan.yaml" # GNU sed
python3 "$SHAPE/adopt-project.py" check --plan "$TMP/plan.yaml" > "$TMP/check" 2>&1
echo "check exit $?: $(grep -o -m1 -E '^plan ok|^FINDING [a-z-]*' "$TMP/check")"
python3 "$SHAPE/adopt-project.py" execute --plan "$TMP/plan.yaml" --yes \
--local-remote-dir "$TMP/remotes" --work-dir "$TMP/work" > "$TMP/exec" 2>&1
echo "execute exit $?: $(grep -o -m1 -E '^adoption verified|^REFUSED [a-z-]*' "$TMP/exec"), leg repositories made: $(ls "$TMP/remotes" | grep -c 'Northwind-')"
if git rev-parse -q --verify adopt/three-repo-shape >/dev/null; then
$SUB clone -q --recurse-submodules -b adopt/three-repo-shape "$TMP/Thing" "$TMP/assembly" > /dev/null 2>&1
echo "the assembly's recursive clone: exit $?"
fi
doneObserved on main 92e29d9 (the same on main 1f90a0f, on PR #245's head 8ce5614, and on that head merged with main b30595c):
== layout A: origin <T>/remotes/Thing.git, url ../dep
check exit 1: FINDING plan-submodule-relative-url
execute exit 0: adoption verified, leg repositories made: 2
the assembly's recursive clone: exit 0
== layout B: origin git@github.com:testorg/Thing.git, url ../dep
check exit 0: plan ok
execute exit 2: REFUSED plan-submodule-relative-url, leg repositories made: 0
check's finding in layout A, as printed:
FINDING plan-submodule-relative-url: the submodule `vendor/lib` (entry `vendor/`) has leg: code, and its url in the source's .gitmodules is the relative `../dep`, which git resolves against the remote of the repository holding that file: from the source, at `<T>/remotes/Thing.git` (its remote `origin`), to `<T>/remotes/dep`, and from the code leg, at `https://github.com/testorg/Northwind-code.git`, to `https://github.com/testorg/dep`; the assembly's recursive clone resolves it from the leg
In layout B, check's NOTE and execute's refusal say what happened:
NOTE `check` resolved the relative url of each submodule the plan keeps in the code leg from that leg's GitHub remote, `https://github.com/testorg/Northwind-code.git`, and each names the same repository from there as from the source: `../dep` of `vendor/lib`, to `https://github.com/testorg/dep`. `execute --local-remote-dir` resolves it again from the bare repository it would create instead, and refuses there, before it creates any leg, a url that names another repository.
REFUSED plan-submodule-relative-url: 1 finding(s) in <T>/plan.yaml, as `check` prints them, but with each relative submodule url resolved from the leg remotes this run would create under --local-remote-dir, where `check` resolves it from GitHub's:
Before #245 (main b30595c), both layouts give check 0, adoption verified and a recursive clone that exits 0.
Why it matters.
checkis the step a person, or an agent following AGENTS.md, runs before the human's yes.--local-remote-diris the path every test, rehearsal and local trial uses. Under it,check's verdict on a relative url is wrong in both directions: it refuses an adoption that works (layout A), and passes oneexecutewill refuse (layout B).- The NOTE and the refusal disclose this, but a disclosure is not a verdict. A plan cannot be checked for the run it will actually have.
Where (main 92e29d9):
adopt-project.py:5121-5126: thechecksubparser, with no--local-remote-dir.execute's is at:5131.docs/cli.md:396-409renderscheck's usage from it.adopt-project.py:3290,cmd_check, which calls_entry_findings(plan, source)at:3309._entry_findingstakeslocal_remote_dir=None(:3202-3204).adopt-project.py:2903-2953,_relative_url_findings, whose docstring sayscheck"decides on GitHub's" (:2916).adopt-project.py:3846-3847and:3920:_refuse_what_check_findstakesexecute'slocal_remote_dir, whichcmd_executepasses at:4251, and hands it to_entry_findings.:3943-3954,_as_resolved_here: the refusal's header.adopt-project.py:2756-2760: the part ofRELATIVE_URL_REMEDIATION(:2750-2760) that says wherecheckresolves from.:56-58: the module docstring's sentence on it.
What would fix it.
checktakes the same--local-remote-dirand passes it to_entry_findingsasexecutedoes, so that the two resolve from the same remotes.- The NOTE, the remediation, the refusal's header and the docstrings that say
checkresolves from GitHub are reworded to say "the remotes this run names". PR adopt-project.py: a relative.gitmodulesurl the plan keeps in a leg is resolved from the source's remote and from the leg's, and where the two differcheckfinds it andexecuterefuses before any leg exists, so a recursive clone no longer fails afteradoption verified(#190) #245 counts eleven places. docs/cli.mdis regenerated. A test runs layouts A and B throughcheck --local-remote-dirandexecute --local-remote-dirand holds the two to one verdict.
Related. #190 and PR #245 (P3-1 there, "left for a follow-up issue"). Not this issue: a kept submodule url that names no repository at all. execute verifies over one from either set of remotes, GitHub's or --local-remote-dir's, and the assembly's recursive clone then fails. That is left to an issue of its own.
Not claimed.
🤖 Generated with Claude Code
Activity
brettheap commentedon Oct 16, 2026
CLAIMED — lane openRepoShape-1, session 300f8f46-004a-4e72-8587-4f192be2e108@Eagle, 2026-10-16T11:30:53Z, for #417
Logged in
lanes/log/openRepoShape-1.mdat7dd42f008cd34dcfa511681931429595eb39d8d3.The three reads (lane-collision-protocol Rule 1):
Merge main 2fac979 into fix/417-adopt-check-local-remote-dir
Merge main 66b230e into fix/652-adopt-source-clone-record