Skip to content

adopt-project.py says adoption verified when a submodule the plan keeps in a leg has a url that names no repository (a relative url that resolves alike from the source and the leg, or an absolute one), so the recursive clone its NEXT: line prints fails; nothing asks whether the url answers #418

Closed

Description

@brettheap

(lane openRepoShape-1, 2026-10-12T16:42:43Z)

Raised by lane openRepoShape-2's review of the #190 branch at b0c959e: its P3-3, in its comment on #190. #190's body asks for this in the sentence before its sign-off: "_verify ... should not print adoption verified for an assembly whose recursive clone cannot fetch a kept submodule". PR #245 closed #190 without it ("P3-3 is left: main shares it, and it is outside #190's cause"), so this issue keeps it tracked. Pre-existing on main before #245 (b30595c), and still there since #245 landed as 161c6c9 (main 92e29d9).

What is wrong. Nothing in plan, check or execute asks whether a kept submodule's url names a repository. When a submodule the plan keeps in a leg has a url that answers nothing, execute still ends adoption verified: every source path is in exactly one place. The NEXT: it prints, git clone --recurse-submodules and then make bootstrap, then fails at the first step. The url can be:

Against GitHub, where check and execute resolve from the same remotes (simulated here with no network: a stub gh, and url.<dir>.insteadOf https://github.com/):

SHAPE=<openRepoShape checkout>
export GIT_AUTHOR_NAME=T GIT_AUTHOR_EMAIL=t@example.invalid GIT_COMMITTER_NAME=T GIT_COMMITTER_EMAIL=t@example.invalid
TMP=$(mktemp -d); mkdir -p "$TMP/gh/testorg" "$TMP/bin"
# GitHub with no network: every https://github.com/ url is a bare repository under $TMP/gh,
# and a stub `gh` makes one for `gh repo create` and answers the rest
printf '[url "%s/gh/"]\n\tinsteadOf = https://github.com/\n' "$TMP" > "$TMP/gitconfig"
export GIT_CONFIG_GLOBAL="$TMP/gitconfig" GIT_ALLOW_PROTOCOL=file PATH="$TMP/bin:$PATH"
cat > "$TMP/bin/gh" <<EOF
#!/bin/sh
case "\$1 \$2" in
  "repo create") git init -q --bare -b main "$TMP/gh/\$3.git" && echo "https://github.com/\$3";;
  "pr create") echo "https://github.com/testorg/Northwind/pull/1";;
  "repo edit") ;;
  *) exit 1;;
esac
EOF
chmod +x "$TMP/bin/gh"
git init -q -b main "$TMP/dep" && (cd "$TMP/dep" && echo dep > value.txt && git add -A && git commit -qm dep)
git init -q -b main "$TMP/Northwind" && cd "$TMP/Northwind" || exit 1
mkdir src docs && printf 'print(1)\n' > src/app.py && printf '# Spec\n' > docs/spec.md
printf '# Northwind\n' > README.md && git add -A && git commit -qm init
git submodule add -q "$TMP/dep" vendor/lib
git config -f .gitmodules submodule.vendor/lib.url ../missing && git add .gitmodules
git commit -qm 'vendor/lib from ../missing'
git clone -q --bare . "$TMP/gh/testorg/Northwind.git" && git remote add origin https://github.com/testorg/Northwind.git
git fetch -q origin && git remote set-head origin main
git clone -q --recurse-submodules https://github.com/testorg/Northwind.git "$TMP/source-clone" > /dev/null 2>&1
echo "the source's own recursive clone: exit $?"
python3 "$SHAPE/adopt-project.py" plan --source "$TMP/Northwind" --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")"
grep '^NOTE `check` resolved' "$TMP/check"
python3 "$SHAPE/adopt-project.py" execute --plan "$TMP/plan.yaml" --yes --work-dir "$TMP/work" > "$TMP/exec" 2>&1
echo "execute exit $?: $(grep -o -m1 -E '^adoption verified|^REFUSED [a-z-]*' "$TMP/exec")"
git clone -q --recurse-submodules -b adopt/three-repo-shape https://github.com/testorg/Northwind.git "$TMP/assembly" > "$TMP/clone" 2>&1
echo "the assembly's recursive clone: exit $?"; grep -m1 '^fatal: clone of' "$TMP/clone" | sed "s#$TMP#<T>#g"

Observed on main 92e29d9 (<T> is the temporary directory; the same on main 1f90a0f, on PR #245's head 8ce5614, and on that head merged with main b30595c):

the source's own recursive clone: exit 1
check exit 0: plan ok
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: `../missing` of `vendor/lib`, to `https://github.com/testorg/missing`. `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.
execute exit 0: adoption verified
the assembly's recursive clone: exit 1
fatal: clone of 'https://github.com/testorg/missing' into submodule path '<T>/assembly/code/vendor/lib' failed

Before #245 (main b30595c), the output is the same without the NOTE.

Under --local-remote-dir, the test path, with a relative and an absolute url:

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 kind in relative absolute; do
  TMP=$(mktemp -d); mkdir "$TMP/remotes"
  git init -q -b main "$TMP/dep" && (cd "$TMP/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/dep" vendor/lib
  # the source's remote sits where the legs will be made, so `../missing` names the same
  # place from the source and from either leg: a repository that is not there
  git init -q --bare -b main "$TMP/remotes/Thing.git" && git remote add origin "$TMP/remotes/Thing.git"
  if [ $kind = relative ]; then url=../missing; else url="$TMP/nowhere.git"; fi
  git config -f .gitmodules submodule.vendor/lib.url "$url" && git add .gitmodules
  git commit -qm "vendor/lib from $url" && git push -q origin main
  echo "== $kind url: $(git config -f .gitmodules submodule.vendor/lib.url | sed "s#$TMP#<T>#")"
  $SUB clone -q --recurse-submodules "$TMP/remotes/Thing.git" "$TMP/source-clone" > /dev/null 2>&1
  echo "the source's own recursive clone: exit $?"
  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")"
  $SUB clone -q --recurse-submodules -b adopt/three-repo-shape "$TMP/Thing" "$TMP/assembly" > "$TMP/clone" 2>&1
  echo "the assembly's recursive clone: exit $?"; grep -m2 fatal "$TMP/clone" | sed "s#$TMP#<T>#g"
done

Observed on main 92e29d9 (the same on main 1f90a0f, on 8ce5614, and on it merged with main b30595c):

== relative url: ../missing
the source's own recursive clone: exit 1
check exit 1: FINDING plan-submodule-relative-url
execute exit 0: adoption verified
the assembly's recursive clone: exit 1
fatal: repository '<T>/remotes/missing' does not exist
fatal: clone of '<T>/remotes/missing' into submodule path '<T>/assembly/code/vendor/lib' failed
== absolute url: <T>/nowhere.git
the source's own recursive clone: exit 1
check exit 0: plan ok
execute exit 0: adoption verified
the assembly's recursive clone: exit 1
fatal: repository '<T>/nowhere.git' does not exist
fatal: clone of '<T>/nowhere.git' into submodule path '<T>/assembly/code/vendor/lib' failed

The relative case's check finding here does not catch this defect. check resolves the leg's url from GitHub's remote, for want of a --local-remote-dir (#417). execute --local-remote-dir resolves both sides alike and again ends adoption verified over the failing clone. Before #245 (main b30595c), check said plan ok in this case too, and every other line was the same.

Why it matters.

  • adoption verified is true about what it checks: every blob is in exactly one place. But the person and the agent read it as the go-ahead for the next step it prints, and that step fails.
  • The source's own recursive clone fails the same way. The adoption reproduces a breakage it could have named first, before two leg repositories were made and the split was pushed.
  • Under --local-remote-dir the answer is certain. On GitHub, whether a url answers can depend on whose credentials ask, so there it can be stated as a NOTE.

Where (main 92e29d9):

  • adopt-project.py:4555-4624, _verify: blob accounting only. adoption verified is at :4623. cmd_execute then prints _next_steps (:4627-4633), whose NEXT: holds the recursive clone at :4632, after any verification that passed, unless gh pr create was refused (:4308-4314).
  • adopt-project.py:2903-2953, _relative_url_findings: it resolves a relative url from both remotes and passes it when the two answers agree (:2933-2935), without asking whether anything answers. The NOTE is at :2946-2953.
  • adopt-project.py:2799-2815, _kept_relative_urls: only a url that RELATIVE_URL_RE (:2747) matches is resolved, so an absolute url is never asked about.

What would fix it.

Related. #190 and PR #245 (P3-3 there, "left"). #417, on check's missing --local-remote-dir. #215, closed by #240: .gitmodules values git's submodule reader refuses are now a check finding. A url git accepts that names no repository is not one of them.

Not claimed.

🤖 Generated with Claude Code

Activity

  1. added this to the v1.1.0 milestone on Oct 14, 2026
  2. brettheap commented on Oct 14, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-1, session 300f8f46-004a-4e72-8587-4f192be2e108@Eagle, 2026-10-14T22:35:57Z, for #418

    Logged in lanes/log/openRepoShape-1.md at d046d1392683e6a2e60e96dff174afc69e4eb6c7.

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

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#418:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#418"`, kept where the row or its body names `#418`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `418` 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

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions