Skip to content

scaffold-project.py, family.py init: after a ruleset refuses the first push, neither exit RULESET_HINT offers can finish: the pull request has no base in the repository gh repo create left empty, and the scaffold's re-run is refused scaffold-remote-exists for its own two empty legs, so a ruleset-protected organisation cannot seed a new project; --reuse-empty-repo is to reuse empty legs, and the hint is to offer only that re-run #462

Closed

Description

@brettheap

Lane: openreposhape-3 (openRepoShape-3)

What happens. Measured on main dd8d0ea with --local-remote-dir. A pre-receive hook, installed through GIT_TEMPLATE_DIR, refuses every push except to seed/* in the repositories it names: a ruleset in miniature, built the way PR #373's test builds it. Every re-run below gets a fresh --work-dir, because the first run's is refused as scaffold-target-exists.

RULESET_HINT offers two exits:

  (1) have an operator holding the bypass right seed the default branch once,
      then re-run this scaffold; or
  (2) push a seed BRANCH and open a pull request:
          git -C <work>/Atlas-spec push -u origin main:seed/scaffold
          gh pr create --repo testorg/Atlas-spec --base main --head seed/scaffold \
              --title 'Seed the spec leg' --body 'Scaffolded shape.'

Exit (2) cannot finish anywhere it is printed. The refused push was the first push into a repository the run had just made with gh repo create, without --add-readme, so that repository is empty. Measured for the scaffold's spec leg and for the family holder:

  • the refused repository holds no ref; its HEAD points at refs/heads/main, which does not exist yet;
  • the hint's own push succeeds and leaves only refs/heads/seed/scaffold;
  • refs/heads/main, the --base, is absent before and after.

The same was measured for the scaffold's root on 45e01f4. gh pr create was not run, because nothing here touches GitHub; GitHub opens no pull request against a base branch the repository lacks. The seed push also makes the repository non-empty: after it, rev-list --count --all is 1.

Exit (1) cannot finish the scaffold.

The ruleset covers all three repositories, so the first push, the spec leg's, is refused:

run exit what happens
first run 2 refused pushing the spec leg; Atlas, Atlas-spec and Atlas-code all exist with 0 commits
re-run 2 scaffold-remote-exists: Atlas.git already exists (its remedy names --reuse-empty-repo)
re-run --reuse-empty-repo 2 reuse Atlas.git (zero commits), then scaffold-remote-exists: Atlas-spec.git already exists, "the two legs must not exist yet ... There is no --force."
the same re-run with the ruleset off, standing for the operator's own run under the bypass 2 the same refusal
the operator pushes the first run's spec clone to main under the bypass, then re-run --reuse-empty-repo 2 scaffold-remote-exists: Atlas-spec.git already exists and has commits

The ruleset covers the root only, so both legs are pushed (1 commit each) and the root is refused:

run exit what happens
re-run --reuse-empty-repo, with or without the ruleset 2 scaffold-remote-exists: Atlas-spec.git already exists and has commits

For family.py init, exit (1) works only in a form the hint does not print. The ruleset refuses the holder:

run exit what happens
first run 2 refused pushing the family holder; Contoso.git exists with 0 commits
re-run 2 family-remote-exists (its remedy names --reuse-empty-repo)
re-run --reuse-empty-repo 2 reuse ... (zero commits), then refused pushing again, with the same hint
re-run --reuse-empty-repo with the ruleset off (the operator) 0 the holder is seeded, 1 commit
the operator pushes the first run's holder clone under the bypass, then re-run --reuse-empty-repo 2 family-remote-exists: ... already exists and has commits
the hint's exit (2) push, then re-run --reuse-empty-repo with the ruleset off 2 the same "has commits" refusal

So a ruleset-protected organisation has no exit that works for a new project. For a new family, the one exit that works (an operator re-runs with --reuse-empty-repo) is not what the hint prints: the hint names neither the flag nor family.py ("re-run this scaffold"), and its literal order, seeding first and then re-running, is refused.

What "empty" means today (the flag's definition, which this issue keeps): zero commits.

  • On disk: git -C <bare> rev-list --count --all is 0, so a commit on any ref counts, seed/scaffold included (scaffold-project.py:local_remote_is_empty, family.py:_local_remote_is_empty).
  • On GitHub: gh repo view succeeds, and gh api repos/<org>/<repo>/commits?per_page=1 either fails with "empty" in its output (GitHub's 409, "Git Repository is empty") or returns [] (scaffold-project.py:remote_is_empty, family.py:_remote_is_empty).
  • So a repository into which an operator has pushed anything is not empty, and neither is one into which someone followed exit (2)'s push.

Where. On dd8d0ea:

  • The hint: scripts/shape_materialize.py:RULESET_HINT (:735-750). On PR shape_materialize.py: RULESET_HINT names the run's tracking branch where it named main, as the pull request's base and the ref the scaffold and the family holder pushed (#324) #373's head 741cbf2 it is at :728, with the fields {local} and {tracking}.
  • Where it is printed:
    • scaffold-project.py:_seed_legs (:1206) and _build_assembly_root (:1321);
    • scripts/family.py:_create_holder_worktree (:986);
    • adopt-project.py:_extract_leg (:3739) and _seed_leg (:3787).
  • The refusal and the flag:
    • scaffold-project.py:_reusable_remotes (:448), whose only reuse is role == "assembly" and reuse_flag and empty;
    • scaffold-project.py:_exists_remedy (:479);
    • scaffold-project.py:--reuse-empty-repo (:359);
    • scaffold-project.py:_create_remotes (:1125), which calls gh repo create at :1152;
    • scaffold-project.py:_leg_visibility (:999);
    • scripts/family.py:_checked_reuse (:903), --reuse-empty-repo (:2854), and _create_holder_remote (:933), which calls gh repo create at :948.
  • Emptiness: scaffold-project.py:remote_is_empty (:225) and local_remote_is_empty (:277); scripts/family.py:_remote_is_empty (:644) and _local_remote_is_empty (:675).
  • Prose:
    • AGENTS.md:37-40;
    • README.md:456-464, which says "--reuse-empty-repo covers exactly one case ... It applies to the root alone — an existing leg is refused whatever it holds";
    • the flag's --help, which docs/cli.md renders.

Why it matters. AGENTS.md's ruleset rule sends a person or an assistant to this hint. In an organisation whose ruleset refuses direct pushes, the hint is the only way forward, and it leads nowhere: a new project cannot be seeded at all, and a new family can be seeded only by a route the hint does not name.

Ruled by Brett Heap on 2026-10-12, his choice verbatim: "Re-run reuses empty legs". Earlier, also his: "Offer only exits that work".

  • scaffold-project.py --reuse-empty-repo reuses each of the two leg repositories exactly as it reuses the assembly root: when the repository exists with zero commits. The re-run of a run whose first push a ruleset refused then finishes when an operator holding the bypass right makes it.
  • RULESET_HINT drops exit (2), both the seed branch and the pull request, and offers that re-run instead: the same command with --reuse-empty-repo and a new --work-dir (or none), run by an operator holding the bypass right.
  • The hint says that the re-run's own pushes are the seed. Measured: under the flag's definition, a leg an operator has pushed into first is no longer empty, and it is refused.

What the writer must keep:

  • A leg with commits is refused, as the root with commits is (scaffold-remote-exists, "... and has commits").
  • "Empty" stays the definition above, asked through the same four functions and not a new one.
  • The root's own rules stay as they are: it is reused only when empty, and a root with commits still names adopt-project.py.
  • A leg that does not exist is created as today. Reuse never deletes, rewrites or force-pushes.
  • A reused leg keeps the visibility the first run gave it. A re-run whose --visibility disagrees is told so, as _leg_visibility already tells it for a reused root.
  • Never --admin, and never a change to the ruleset (AGENTS.md).

Settled by the second ruling below: a run refused after a leg was pushed. When the ruleset covers only the root, or only the code leg, the leg already pushed holds the scaffold's seed commit (measured: both legs at 1 commit when only the root refuses), so the reused-legs re-run is still refused for it. "Offer only exits that work" means the hint printed there (_build_assembly_root, and _seed_legs for the second leg) must not offer the bare re-run. Either it names the repositories that hold commits and says the re-run needs them deleted first (gh repo delete, as README.md:456-460 already describes), or that case goes back to Brett.

What else changes:

Source. PR #373 (#324), Left out, second bullet: "gh pr create --base <tracking> cannot run in any of the five places, for main as for any other branch: the repository the push was refused at is empty (gh repo create is called without a README), so the pull request has no base to land on." Exit (1)'s re-run failing, scaffold-remote-exists for the legs with and without the flag, is a triager's finding on 53bee5b, re-measured above on dd8d0ea.

Related:

Ruled for the case above (Brett Heap, 2026-10-12): "Accept the scaffold's own seed". A leg that already holds the seed after a partial refusal is accepted too. The re-run accepts a leg whose only commit has exactly the tree this scaffold would push. So the re-run stays one command, and nothing is deleted. Any other non-empty leg is still refused.

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-3, session 43f4ac63-0116-41de-977d-afc23cdf7fdf@Eagle, 2026-10-12T19:35:37Z, for #462

    Logged in lanes/log/openRepoShape-3.md at 4c3a0cb9b3bed146be8acec31523e8ba7c57c83e.

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

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#462:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#462"`, kept where the row or its body names `#462`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `462` as a whole token:
       none
    
  3. added 5 commits that reference this issue on Oct 13, 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

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions