Repository navigation
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
Description
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
HEADpoints atrefs/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 --allis 0, so a commit on any ref counts,seed/scaffoldincluded (scaffold-project.py:local_remote_is_empty, family.py:_local_remote_is_empty). - On GitHub:
gh repo viewsucceeds, andgh api repos/<org>/<repo>/commits?per_page=1either 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_HINTnames the run's tracking branch where it namedmain, 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).
- scaffold-project.py:
- The refusal and the flag:
- scaffold-project.py:
_reusable_remotes(:448), whose only reuse isrole == "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 callsgh repo createat :1152; - scaffold-project.py:
_leg_visibility(:999); - scripts/family.py:
_checked_reuse(:903),--reuse-empty-repo(:2854), and_create_holder_remote(:933), which callsgh repo createat :948.
- scaffold-project.py:
- Emptiness: scaffold-project.py:
remote_is_empty(:225) andlocal_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-repocovers 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-reporeuses 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_HINTdrops exit (2), both the seed branch and the pull request, and offers that re-run instead: the same command with--reuse-empty-repoand 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
--visibilitydisagrees is told so, as_leg_visibilityalready 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:
- The family holder.
family.py init --reuse-empty-repoalready reuses an empty holder, and the operator's re-run with it finishes (measured, exit 0), so family.py needs no new reuse. Its hint changes the same way: one exit,family.py initre-run with--reuse-empty-repoby an operator holding the bypass right. The hint must stop saying "re-run this scaffold". - AGENTS.md:37-40. It now reads "the exits are in the scaffold's own failure message (seed by pull request, or ask an operator with the bypass right)". "Seed by pull request" goes, and what is left is one exit: an operator with the bypass right re-runs the same command with
--reuse-empty-repo. README.md:456-464 ("It applies to the root alone") and the flag's--help, and with it docs/cli.md, change with the flag. - Adoption. adopt-project.py prints the same hint from both legs, and there a re-run into existing legs is
leg-remote-exists, which this ruling does not touch.fix/adopt-late-failure-notice(adopt-project.py: a failure AFTER both legs are pushed (the split-branch push, the mount, the split commit) prints onlyREFUSED <step>with no notice of what already exists and no stdout flush, so the combined log shows REFUSED before thebare …lines and the re-run meetsleg-remote-existsunexplained #188's branch, no PR open) gives adoption its ownADOPT_RULESET_HINT, whose one exit is an operator with the bypass right. If that branch has not landed first, the adoption's two calls need a text of their own here, because a hint that says--reuse-empty-repois wrong foradopt-project.py. - Built on PR shape_materialize.py:
RULESET_HINTnames the run's tracking branch where it namedmain, as the pull request's base and the ref the scaffold and the family holder pushed (#324) #373. Lane 3's open PR shape_materialize.py:RULESET_HINTnames the run's tracking branch where it namedmain, as the pull request's base and the ref the scaffold and the family holder pushed (#324) #373 (RULESET_HINThard-codesmainas the local branch and as--base, so under--tracking-branch trunkthe hint'sgit push -u origin main:seed/scaffoldnames a branch the work clone does not have and itsgh pr create --base mainopens against the wrong base #324) rewrites this same hint for--tracking-branchand adds tests/test_ruleset_hint_tracking_branch.py. That test pins the hint's text as a literal, asserts--base {tracking}, and runs the printed push for the scaffold's legs, its root and the holder. Write this fix on top of shape_materialize.py:RULESET_HINTnames the run's tracking branch where it namedmain, as the pull request's base and the ref the scaffold and the family holder pushed (#324) #373, after it lands or on its head. The--baseline, the push line, and that test's literal and push runs go or change with them. Itspre-receiveharness is the one for this fix's tests, with the hook turned off standing in for the operator's bypass.
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:
- RULESET_HINT advises
push -u origin ...from an extracted leg whoseoriginfilter-repo removed, and says "listed above" of lines a combined log prints after it #177 (open): the adoption'spush -u originfrom an extracted leg whoseoriginfilter-repo removed; - adopt-project.py: a failure AFTER both legs are pushed (the split-branch push, the mount, the split commit) prints only
REFUSED <step>with no notice of what already exists and no stdout flush, so the combined log shows REFUSED before thebare …lines and the re-run meetsleg-remote-existsunexplained #188:fix/adopt-late-failure-notice, the adoption's own hint; RULESET_HINThard-codesmainas the local branch and as--base, so under--tracking-branch trunkthe hint'sgit push -u origin main:seed/scaffoldnames a branch the work clone does not have and itsgh pr create --base mainopens against the wrong base #324 / PR shape_materialize.py:RULESET_HINTnames the run's tracking branch where it namedmain, as the pull request's base and the ref the scaffold and the family holder pushed (#324) #373: the tracking branch in this hint.
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
brettheap commentedon Oct 12, 2026
CLAIMED — lane openRepoShape-3, session 43f4ac63-0116-41de-977d-afc23cdf7fdf@Eagle, 2026-10-12T19:35:37Z, for #462
Logged in
lanes/log/openRepoShape-3.mdat4c3a0cb9b3bed146be8acec31523e8ba7c57c83e.The three reads (lane-collision-protocol Rule 1):
Merge origin/main (aa4e1f5) into fix/scaffold-reuse-seeded-legs
Merge origin/main (da19735) into fix/scaffold-reuse-seeded-legs
Merge origin/main (af01068) into fix/scaffold-reuse-seeded-legs
Merge origin/main (fc48854) into fix/scaffold-reuse-seeded-legs
scaffold-project.py and family.py init: `--reuse-empty-repo` reuses a…
Merge origin/main (8cd7dbe) into fix/family-commands-and-branch
Merge origin/main (d52b100, #462's `--reuse-empty-repo` change) into …
Merge origin/main (d41cc29) into fix/adopt-project-must-be-source-nam…
Merge origin/main (d41cc29) into fix/adopt-gitattributes-lf-block: th…
Merge origin/main d41cc29 into fix/update-shape-accept-local-and-merg…
Merge origin/main d41cc29 into fix/adopt-root-kept-symlinks: #435's t…
family.py: `init` refuses to land in the standard's checkout and name…
Merge origin/main (89c6a6c) into fix/installed-doctor-keeps-clone: th…
openRepoShape --doctor: the installed form keeps the clone of the sta…
Merge origin/main (cf118ff, #513) into fix/update-shape-accept-local-…
Merge origin/main (8beb3d9, #512) into fix/adopt-gitattributes-lf-blo…
tests.yml and adopt-project.py: the Windows job installs git-filter-r…
scaffold-project.py: a leg `gh` sees and git cannot read is refused `…
family.py and scaffold-project.py: `--dry-run` asks the real run's ow…