Repository navigation
the Free-plan hint's reads: a public reused holder gets the "private" NOTE and hint, an unread root is taken as the run's visibility, and an unread plan is silent #660
Description
Both creating tools and family.py init should read the visibility of the repository the validate workflow runs in, say so when they cannot, and not take the run's own --visibility for it.
From the checklist triage of 2026-10-15, batch 3; Brett Heap approved its recommendations at 2026-10-16T12:14:51Z. One pull request covers the lines below.
Lands after: #621. Its _root_visibility and ROOT_VISIBILITY_UNREAD are the model, and it leaves scaffold-project.py's reused-root fallback and family.py alone.
Decision applied: DB4 (option 1) and DB11 (option 1). D5(b) of the morning triage is the same rule for the root: the hint goes by the root.
Lines
Line numbers are at the commits named; main has moved since.
- L2-58:
family.py init --reuse-empty-repogoes by--visibility, the run's, and never asks the reused holder's own visibility, so a public holder gets the "private" NOTE and the Free-plan hint. Where: scripts/family.py_finish_holder_remote(:1335-1357;args.visibility in ("private", "internal")at :1347,free_plan_secret_hintat :1353-1356),--visibilitydefaultprivate(:5730-5731),_checked_reuse(:1246) asks only whether the repository is empty, the manifest'svisibility:is filled fromargs.visibility(:1167; templates/family-root/family.yaml:46);family.pyhas nogh repo view --json visibilitycall. Effort S; all. On main396c0ff4: with a stubghthat knowstestorg/Orbitas empty and PUBLIC,init --reuse-empty-repo --no-pushexits 0 and prints "testorg/Orbit is private and its members will be too" and "testorg/Orbit and its members are private, so org-level ... resolve to EMPTY"; the stub's call log holds no visibility question (probeout58.txt).
Rides along (backlog; tick it here if the same change covers it)
- L2-57: a reused root whose visibility cannot be read is taken as the run's visibility, so a public root can get the Free-plan hint. Where: scaffold-project.py
_create_remotes(remote_visibility(...) or args.visibility, :1188),_reused_leg_visibility(return seen or args.visibility, :1227),_leg_visibility(return reused_visibility or "private", :1052),_private_leg_note(:1230-1277; the root gate at :1271-1274). Effort S-M; all. On main396c0ff4: a stubghthat knowstestorg/Atlasas empty and PUBLIC and answersrepo view --json visibilitywith HTTP 502 makes--reuse-empty-repo --no-pushexit 0 and print "testorg/Atlas-spec and testorg/Atlas-code are private, so org-level ... resolve to EMPTY"; with the read working the same run prints no hint (probeout57fail.txt,out57ok.txt). After adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621 the same run says "testorg/Atlas is private" (its subject becomes the root), which is just as false. - L2-59:
free_plan_secret_hintreturns None for a plan it cannot read, exactly as for a paid plan. Where: scripts/repo_shape.pyfree_plan_secret_hint(:1028-1056; the docstring at :1041-1043 chooses silence over a guess;.plan.nameread at :1045-1055); callers scaffold-project.py:1272, adopt-project.py:7812, scripts/family.py:1353; README.md:628-631 says "the creating tools check and say so". Effort M; all. Read, not measured: whether a non-owner's token readsplanis unmeasured (DB4).repo_shape.pyis a pinned shape copy, so the plan reader goes inscripts/shape_materialize.py(free, other or unread) andFREE_PLAN_HINT's text stays; adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621 says it leaves the plan read alone by design. - L2-71:
family.py init's Free-plan hint says "<holder> and its members are private", which names members that do not exist atinitand thatinitdoes not make. Where: scripts/family.py_finish_holder_remote(the hint's subject at:1353-1354, the NOTE's "and its members will be too" at:1348-1349); scripts/repo_shape.py:1013-1014,:1019-1020. Effort S; all. On main4cf5fb07: with a stubghthat answers planfreefor any organisation,family.py init --org testorg --family Orbit --no-push --work-dir <d>exits 0 and prints "testorg/Orbit and its members are private, so org-level". No member exists yet at that point. DB11: decide the NOTE's "and its members will be too" (:1348-1349) in the same edit.
Not a duplicate: only #602 mentions the hint, and it is L2-17 and L2-44 (the subject), not the reads.
Each line above is marked → this issue on its tracking issue. Tick it there too when this closes.
The lines as written on the tracking issues
L2-58 (P3; scripts/family.py, #602 follow-up):
family.py init --reuse-empty-repogoes by--visibility, the run's, and never asks the reused holder's own visibility, so a public holder gets the "private" NOTE and the Free-plan hint.
- At 55faad2
_finish_holder_remotetestsargs.visibility in ("private", "internal")(scripts/family.py:1347), prints the NOTE, and callsfree_plan_secret_hint(args.org, repository, f"{repository} and its members are")(:1353-1356).--visibilitydefaults toprivate(:5730-5731),_checked_reuse(:1246) asks only whether the repository is empty, andfamily.pyhas nogh repo view --json visibilitycall. The holder is the repository whosevalidateworkflow reads the secrets, so its own visibility decides, as the root's does for the scaffold since scaffold-project.py re-run: a leg that gh sees and git cannot read gets a generic failure, and the reuse run's "two new legs" and private-leg text are wrong #542.- Measured on a scratch export of main with a stub
ghthat knowstestorg/Orbitas PUBLIC and empty:init --reuse-empty-repo --no-pushexits 0 and prints "testorg/Orbit is private and its members will be too" and "testorg/Orbit and its members are private, so org-level SHAPE_LEGS_APP_ID / SHAPE_LEGS_APP_PRIVATE_KEY resolve to EMPTY". The stub saw no visibility question. adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621 does not touchfamily.py.- Direction: when the holder is reused and the remote is not local, read its visibility with the reader from L2-57, gate the NOTE and the hint on it, and say so when it cannot be read. The manifest's
visibility:(templates/family-root/family.yaml:46, filled fromargs.visibilityatscripts/family.py:1167) is L2-23's question for the family.
L2-57 (P3; scaffold-project.py, #602 review): a reused root whose visibility cannot be read is taken as the run's visibility, so a public root can get the Free-plan hint.
- At 55faad2
_create_remotesreads a reused root withremote_visibility(...) or args.visibility(scaffold-project.py:1174-1175),_reused_leg_visibilityendsreturn seen or args.visibility(:1213), and_leg_visibilityfalls back to"private"(:1031-1038)._private_leg_notethen goes by that value (:1257-1260). The scaffold-project.py andfamily.py init: the dry runs skip the target-exists and folder checks, and anygh repo viewfailure reads as "does not exist" #609 writer's first edge case names these callers: anyghfailure falls back to the run's visibility.- Measured on a scratch export of main with a stub
ghthat knowstestorg/Atlasas PUBLIC and answers itsrepo view --json visibilitywith HTTP 502:--reuse-empty-repo --no-pushexits 0 and prints "testorg/Atlas-spec and testorg/Atlas-code are private, so org-level SHAPE_LEGS_APP_ID / SHAPE_LEGS_APP_PRIVATE_KEY resolve to EMPTY", which is false of a public root. After adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621 the same run says "testorg/Atlas is private" (rv602's F1,scaffold-unread-public-root-out.txt). The read is unlikely to fail a moment after the scaffold read the same root, so this is rare.- Direction: adopt's
_root_visibilityand itsROOT_VISIBILITY_UNREADNOTE (adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621) are the model. Move the reader and the NOTE intoscripts/shape_materialize.py, which is not a shape copy, so both tools follow the one rule adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621's body states.
L2-59 (P3; scripts/repo_shape.py, #602 review):
free_plan_secret_hintreturns None for a plan it cannot read, exactly as for a paid plan, so a closed root on an organisation whose plan does not read back asfreegets neither the hint nor a word.
scripts/repo_shape.py:1050-1055at 55faad2: aghthat cannot start, a nonzerogh api orgs/<org>and any plan name other thanfreeall return None, and the docstring says it prints nothing rather than guess (:1042-1043). The scaffold, adopt andfamily.py initcall it (scaffold-project.py:1258,adopt-project.py:7682,scripts/family.py:1353). README.md:628-631 says "the creating tools check and say so".- A token that cannot read the organisation's plan therefore hides a true hint in silence, in all three tools. adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621 names an unread ROOT now; an unread PLAN is still silent (rv602's F2, pre-existing).
- Direction:
repo_shape.pyis a pinned shape copy, so a change to it re-pins every project. Put the plan read in a helper inscripts/shape_materialize.pythat answers free, other or unread, keepFREE_PLAN_HINT's text, and have the callers print a named NOTE for unread. Bundle any change torepo_shape.pyitself with its next re-pin.
L2-71 (nit; scripts/family.py, #621 fix round):
family.py init's Free-plan hint says "<holder> and its members are private", which names members that do not exist atinitand thatinitdoes not make.
- At 4cf5fb0
_finish_holder_remotepassesf"{repository} and its members are"as the hint's subject (scripts/family.py:1353-1354), and the NOTE above it says "and its members will be too" (:1348-1349). The hint reads "{what} private, so org-level SHAPE_LEGS_APP_ID / SHAPE_LEGS_APP_PRIVATE_KEY resolve to EMPTY" (scripts/repo_shape.py:1013-1014), and its twogh secret setlines name the holder alone (:1019-1020). Each member is a whole project with its own repositories (scripts/family.py:22-24).- Measured on a scratch export of main with a stub
ghthat answers planfreefor any organisation:family.py init --org testorg --family Orbit --no-push --work-dir <d>exits 0 and prints "testorg/Orbit and its members are private, so org-level". No member exists yet at that point.- adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621 (head 10dda17, not on main) changes the scaffold's and adopt's subject to the root alone (
scaffold-project.py:1276-1278andadopt-project.py:7859-7860at 10dda17,f"{root} is") and leavesscripts/family.py:1353-1354. f621r left it as it was: "true of the holder it creates but names the members too; no change" (impl602-free-plan-hint-root/progress.md).- Direction:
f"{repository} is", as the other two say it after adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621, since the secrets that matter are the ones the holder's ownvalidateworkflow reads (L2-58). Decide the NOTE's "its members will be too" with it, because it makes the same claim about members to come. A grep oftests/,README.md,AGENTS.mdanddocs/finds neither sentence, andtests/test_free_plan_hint.py:112-123only checks that each tool calls the hint with its own organisation.scripts/repo_shape.pyis not touched, so no project re-pins.
Activity
brettheap commentedon Oct 16, 2026
CLAIMED — lane openRepoShape-2, session 81650e29-c906-4fe8-96c9-55580bfe0c44@Eagle, 2026-10-16T12:44:28Z, for #660
Logged in
lanes/log/openRepoShape-2.mdatd214009d0aa6e509231a19b1a7e3cdae3e6b2436.The three reads (lane-collision-protocol Rule 1):
shape_materialize.py: an owner gh says is no organisation has no plan…
scaffold-project.py, adopt-project.py and family.py: the Free-plan se…
Merge main bf1f099 into fix/688-family-init-values
shape_materialize.py, scaffold-project.py and family.py: the folder r…