Skip to content

adopt-project.py plan never compares --project (or --org) with the repository it adopts in place, so a source named billing-service is refused with a remediation that steers to --project BillingService, which plans and checks plan ok against acme/billing-service, and execute then acts on acme/BillingService: a raw git ls-remote failure when no such repository exists, and, when a different repository of that name does, both legs, the split branch, the pull request and the topic on that repository #499

Closed

Description

@brettheap

Lane: openreposhape-4 (openRepoShape-4)

(lane openRepoShape-4, 2026-10-12T23:17Z) Found by lane 4's v1.0.0 README/AGENTS.md audit (finding auA-02), re-verified on main 437a360. Lane 4 claims it.

What is wrong. AGENTS.md's adoption steps say plan --source <org/repo|path> --project <Project> (AGENTS.md:118-130), and README says the source "KEEPS ITS NAME" (README.md:729-744). Neither says what <Project> must be. plan --help does: "for an in-place adoption it is the source repository's own name" (adopt-project.py:5991-5992). Nothing enforces it.

A source with an ordinary hyphenated name, billing-service, is refused naming-unclassified with "Remediation: --project takes one CamelCase token …". A person or an assistant does what that says and passes --project BillingService, and plan and check both pass. The report reads WHAT WILL HAPPEN, against acme/billing-service on one line and ONE split commit on branch adopt/three-repo-shape of BillingService on the next. execute against GitHub then acts on https://github.com/acme/BillingService.git, not on the source.

The same holds for any mismatch. A typo (--project Atlsa for acme/Atlas) and an --org that is not the source's (--org acme-labs for acme/Atlas) both plan and check plan ok.

Reproduction. Offline, on Linux. $U is a checkout of main 437a360; git-filter-repo is on PATH; a gh that exits 97 stands first on PATH; GIT_ALLOW_PROTOCOL=file is set; identities come from env. GitHub is emulated, the route lane 3 used for #479: a probe-only global git config holds url."$R/github/".insteadOf = https://github.com/, so https://github.com/acme/X.git resolves to the local bare repository $R/github/acme/X.git.

  1. A source $R/billing-service with specs/a.md, src/a.py and README.md, one commit, and origin set to https://github.com/acme/billing-service.git. It is published as $R/github/acme/billing-service.git.
  2. adopt-project.py plan --source $R/billing-service --project billing-service --elected-by 'Probe Human':
    REFUSED naming-unclassified: 'billing-service' matches no family in the naming policy
    Remediation: --project takes one CamelCase token with no hyphen, underscore, dot or space.
    
    Exit 2.
  3. The same with --project BillingService: exit 0. The plan records source: repository: acme/billing-service, project: BillingService and legs: assembly: BillingService.
  4. check --plan plan.yaml, exit 0:
    WHAT WILL HAPPEN, against acme/billing-service @ e1972f902b89:
      create BillingService-spec and BillingService-code (private)
      ONE split commit on branch adopt/three-repo-shape of BillingService: `git rm -r` those paths, mount the legs at spec/ and code/
    plan ok
    
  5. execute --plan plan.yaml --yes in GitHub mode, with no acme/BillingService:
    REFUSED a git or gh command failed. THE EXACT COMMAND WAS:
        git ls-remote https://github.com/acme/BillingService.git refs/heads/adopt/three-repo-shape
        exit 128
    --- output ---
    fatal: '<R>/github/acme/BillingService.git' does not appear to be a git repository
    
    Exit 2. This comes before the confirmation and before any leg is created.
  6. Create a DIFFERENT repository, acme/BillingService (one unrelated commit), and run step 5 again. It gets past the split-branch check and the yes, as far as:
    creating the leg repositories
    REFUSED a git or gh command failed. THE EXACT COMMAND WAS:
        gh repo create acme/BillingService-spec --private --description BillingService — spec leg, extracted from acme/BillingService with history
        exit 97
    
    Only the stub stopped it. With a real gh, execute goes on to create both legs, push the split commit to acme/BillingService's adopt/three-repo-shape, open the pull request there and set the topic on it (see Why).
  7. A CamelCase source acme/Atlas:
    • planned with --project Atlsa: plan and check both exit 0, and the report reads WHAT WILL HAPPEN, against acme/Atlas … then ONE split commit … of Atlsa;
    • planned with --project Atlas --org acme-labs: the plan records repository: acme/Atlas and org: acme-labs, and check says plan ok.

Why. At 437a360:

  • The source's own name is read but never compared. Source.open reads the source's org/repo from its origin (adopt-project.py:743, _remote_repository :880-899), or takes it from --source org/repo (:769). plan uses only the org half, as the default for --org (:1788). The name half is written to the plan (:1434) and printed (:2061, :3513-3514), and never compared with --project.
  • --project alone names all three repositories (_names, :5872-5874). In GitHub mode _repository_urls (:4483-4521) builds every repository and URL execute acts on from --org and --project (https://github.com/{org}/{name}.git, :4512). That covers:
    • the split-branch probe (:4049, reached at :5019, before _confirm at :5024);
    • the legs (_create_leg_remotes, :5040);
    • the split push (:4977-4979);
    • gh pr create --repo (:4744);
    • the topic (:4822).
  • Local tests cannot see it. Under --local-remote-dir, the assembly URL IS the source path (:4520), so no local test or probe ever meets the mismatch.
  • The remediation steers to the wrong value. _check_names (:5883-5886) tells the person to retype the name. It does not say that the assembly root of an in-place adoption cannot be renamed by a flag.

Proposed fix (for review on the PR).

  • plan refuses when the source names a repository (source.repository is not None) and <org>/<project> is not that repository. The refusal has a code of its own, names both, and comes before anything is written. check and execute ask the same of the plan, so a hand-edited project: or org: is caught too.
  • Options for Brett:
    1. Compare case-insensitively or exactly. GitHub resolves acme/atlas and acme/Atlas to one repository, and the naming policy accepts a lowercase name.
    2. What the refusal tells a person whose repository's own name fails the naming policy (billing-service):
      • (a) that it cannot be adopted in place under that name; renaming it is their decision, made first on GitHub (gh repo rename), and then they plan again; or
      • (b) only that it cannot be adopted in place, and that a project can stay single (single-repository.yaml).
        Either way, the naming-unclassified hint to retype --project is not offered for an in-place adoption.
    3. A local source with no origin has no name to compare. Either keep today's behaviour (--org required, --project not compared), or compare --project with the directory's name.
  • AGENTS.md step 1's clause, that --project is the source's own name, is README.md and AGENTS.md: an accuracy pass for v1.0.0, every command, flag, exit code, refusal code and step they state checked against the tools on main, so the tag's "README and AGENTS.md accurate" criterion holds #483's text once this lands.

How to test. In the adoption plan tests:

  • A source whose origin is acme/billing-service, planned with --project BillingService, is refused, and no plan file is written.
  • acme/Atlas planned with --project Atlsa is refused, and so is acme/Atlas with --org acme-labs.
  • acme/Atlas with --project Atlas passes. acme/atlas with --project Atlas behaves as option 1 says.
  • check refuses a plan whose project: was edited to another name.
  • Run tests/test_shape_doctor.py and tests/test_scaffold_leg_path_collisions.py too, because the doctor reuses adopt's plan reader.

Why v1.0.0. Hyphenated repository names are ordinary, and the procedure cannot succeed for them. The tool's own remediation then steers to a value that makes execute act on a different repository from the one it says it is adopting. Proposed P1.

Parent: #457 (adopt).

Related: #427 (the org read from origin), #479 (the base branch).

Activity

  1. added this to the v1.0.0 milestone on Oct 12, 2026
  2. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-4, session bf1c72df-1c56-4ac9-aace-3ddd139d7ea3@Eagle, 2026-10-12T23:31:22Z, for #499

    Logged in lanes/log/openRepoShape-4.md at 6a1230ecd31341f1e15fa97c9ac69166ce1b7383.

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

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

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions