Skip to content

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

Closed

Description

@brettheap

(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. check exits 1 with FINDING plan-submodule-relative-url. execute verifies, and the assembly's recursive clone succeeds.
  • Layout B has the source's remote on GitHub and the legs local. check says plan ok, with a NOTE. execute refuses 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
done

Observed 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.

  • check is the step a person, or an agent following AGENTS.md, runs before the human's yes.
  • --local-remote-dir is 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 one execute will 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: the check subparser, with no --local-remote-dir. execute's is at :5131. docs/cli.md:396-409 renders check's usage from it.
  • adopt-project.py:3290, cmd_check, which calls _entry_findings(plan, source) at :3309. _entry_findings takes local_remote_dir=None (:3202-3204).
  • adopt-project.py:2903-2953, _relative_url_findings, whose docstring says check "decides on GitHub's" (:2916).
  • adopt-project.py:3846-3847 and :3920: _refuse_what_check_finds takes execute's local_remote_dir, which cmd_execute passes at :4251, and hands it to _entry_findings. :3943-3954, _as_resolved_here: the refusal's header.
  • adopt-project.py:2756-2760: the part of RELATIVE_URL_REMEDIATION (:2750-2760) that says where check resolves from. :56-58: the module docstring's sentence on it.

What would fix it.

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

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

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-1, session 300f8f46-004a-4e72-8587-4f192be2e108@Eagle, 2026-10-16T11:30:53Z, for #417

    Logged in lanes/log/openRepoShape-1.md at 7dd42f008cd34dcfa511681931429595eb39d8d3.

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

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#417:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#417"`, kept where the row or its body names `#417`:
    618	adopt-project.py: a kept submodule url on this disk where `git ls-remote` reaches no repository is a finding `execute` refuses before any leg, so it no longer ends `adoption verified` over a recursive clone that fails (#418)	fix/418-adopt-submodule-url-answers	MERGED
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `417` as a whole token:
       none
    
  3. added a commit that references this issue on Oct 16, 2026
  4. added a commit that references this issue on Oct 16, 2026
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