Skip to content

family.py: three lines a remedy prints name no branch and are refused family-on-tracking-branch when typed there: add --dry-run's and bump --dry-run's member-duplicate-id, and init's family-holder-already-here (#1201 follow-up) #1228

Open

Description

@brettheap

(lane openRepoShape-5, 2026-10-23)

Goal: every add, bump and remove line that a family.py remedy prints says the branch it runs on, whichever route reached the refusal: the dry runs of add and bump that end in member-duplicate-id, and the family-holder-already-here that init ends in, say it as #1201 makes member-unknown and member-already-present do; and the one mutant of #1201's rows that survives is killed.

Problem

This issue applies once #1201 (#1127) has landed. #1201 is stacked on #1117: its base is #1117's branch, fix/1005-family-remove-add-family-root. _family_line, _cited and _off_the_tracking_branch are in neither until both have landed (origin/main 50ce8ab15 has none of the three in scripts/family.py). Every line number below is scripts/family.py at #1201's head eac177af9.

#1201 makes every add, bump and remove line a remedy names one spelling, _family_line, and puts a clause before the line in the remedies that are said before _checked_branch asks the holder's branch: "on a branch other than the holder's tracking branch, which add refuses (git -C <holder> switch -c <branch> makes one), " (_off_the_tracking_branch, :2312), for bump's member-unknown and add's member-already-present. The two member-duplicate-id remedies carry no clause, and the code says why (:6672-6675 and :8722-8725): "A real add asks this after _checked_branch, so no branch is named" (bump in the second). That is true of a real run only. Three printed lines are reached without _checked_branch having run, so on the tracking branch, where a fresh clone stands, each is refused when typed:

  • (a) add --dry-run's member-duplicate-id. Under --dry-run, cmd_add skips _checked_branch (:7586, branch = None if args.dry_run else _checked_branch(...)) and still asks _checked_member_id (:7606) before the dry run leaves (:7610). On the tracking branch, add --dry-run --member InkRouter/IRRS2, a renamed copy of a member the family pins, is refused member-duplicate-id. Its remedy (:6683-6692) prints a remove line and a bump line and names no branch. The remove line, typed in bash, is REFUSED family-on-tracking-branch. The review typed the remove. The bump line beside it meets the same _checked_branch in cmd_bump (:9477), so by reading it is refused alike once --to is a commit; that one was not typed.
  • (b) bump --dry-run's member-duplicate-id. _bump_dry_run asks _checked_bumped_id (:9245) and nothing asks the branch first; _branch_said comes after (:9270). Its remedy (:8733-8741) prints a remove line only, and typed on the tracking branch it is REFUSED family-on-tracking-branch.
  • (c) init's family-holder-already-here. Its remedy (:2503) prints _add_command(into / family, args.org) between plain backticks: no branch clause, and not through _cited (:2303). Typed on the holder init just made, which stands on the tracking branch, the add is REFUSED family-on-tracking-branch. Under a folder whose name holds a backtick, the first backticked span ends at the path's backtick, which is RV1117's N6 shape that family.py: the add, bump and remove lines its remedies name begin with the interpreter and the script's path, quote the row's name, fill in the family's org, say the branch they run on, and member-already-present prints its remove (#1127) #1201 mends for every other remedy.

Each chain still ends in a refusal that prints its own switch -c, so none is a dead end; the cost is a round trip, and these are the three lines that #1201's Goal now sets aside ("three other lines are in Limits"). #1201's Limits names all three and leaves them to a follow-up, with the three NITs below. Its Limits also names two other follow-ups that are not asked here: the family-root-missing lines that say a bare family.py init (:558, :10386) and member-ambiguous's remedy, which has no line to type (:607).

Measured

Lane 5's independent review of #1201 (#1127), rv1201, SHOULD-1 and NIT-1 to NIT-3 (#1201 issuecomment-6372152028), at eac177af9, which is the PR's head still (read back with gh pr view 1201, updated 2026-10-23T18:40:12Z). The review's probe file (not in the PR) holds the rows p6, p7 and p9, and all 27 of its rows passed at that head, so what each row asserts is what the head does:

  • p6. A holder on main; add --dry-run --member InkRouter/IRRS2, whose project.yaml records id irrs, which the row IRRS pins. The answer is REFUSED member-duplicate-id: IRRS2 is already a member of InkRouter under another name: ..., and its remedy prints python3 <path>/scripts/family.py remove --family-root <holder> --member IRRS and, beside it, the same line with bump and --to <commit>. The remove typed in bash exits 2: REFUSED family-on-tracking-branch, " is on 'main', which family.yaml declares as the holder's tracking branch, and remove will not write a commit whose only next step is a push to the default branch", with its own remedy's switch -c family/remove-irrs.
  • p7. bump --dry-run to a commit whose project.yaml records another row's id. The remedy's one line is python3 scripts/family.py remove --family-root <holder> --member IRSS; typed, it exits 2, REFUSED family-on-tracking-branch, with switch -c family/remove-irss.
  • p9[plain]. init re-run with --into the folder init made. The answer is REFUSED family-holder-already-here: ... so there is nothing to init, and its remedy is the add line (python3 <path>/scripts/family.py add --family-root <holder> --member InkRouter/<Project>) between backticks, then "grows it, and make siblings ...". The probe asserts that switch -c is nowhere in the output. The add, typed in bash with <Project> replaced by Anything, exits 2: REFUSED family-on-tracking-branch.
  • p9, the backtick folder. The folder is named a`b. The spans between backticks that the probe reads out of the remedy are python3 .../family.py add --family-root '.../a (cut at the path's backtick) and grows it, and (prose, read as a span).

The three NITs, as the review measured them: mutant R5 (the branch of member-already-present built from the row's name, not its id:) passes all 12 rows of tests/test_family_remedy_lines.py; the rows' typed() asserts the printed interpreter's name and runs the script by sys.executable, while the 12 lines typed through real shells (bash, dash and zsh, with python3 from PATH) passed 12 of 12; and member-already-present suggests one branch, family/bump-<id>, for its bump and its remove line. The drafter re-read each line number above in scripts/family.py and in the test file at eac177af9.

Suggested fix

The three lines. The review's optional code fix, with what reading the code at the head adds:

  1. (a) and (b): ask the branch when the run is a dry run. Give _checked_member_id (:6642) and _checked_bumped_id (:8695) a dry_run flag, false by default, so _mount_and_pin's second ask (:7382) and cmd_bump's real-run asks (:9302, :9379) are unchanged. cmd_add passes args.dry_run at :7606; _bump_dry_run passes true at :9245. When it is set, the remedy begins its line or lines with _off_the_tracking_branch(family, refuses, suggested) and ends with BRANCH_LEFT, as member-already-present does (:6547-6555). refuses is "bump and remove refuse" for (a), whose remedy names both lines, and "remove refuses" for (b). suggested is the branch the remove line's own refusal gives (:10329): _suggested_branch("remove", str(row.get("id") or name)), which is family/remove-<id> here (p6 and p7 print family/remove-irrs and family/remove-irss), so the clause and the refusal after it agree. The two code comments (:6672-6675, :8722-8725) and _off_the_tracking_branch's docstring (:2314-2325, which names member-unknown and member-already-present as the two it serves) are reworded: a dry run says the refusal before the branch is read, a real run after it.
  2. (c): print the line through _cited and with the clause. _cited(_add_command(into / family, args.org)), and the clause before it. Two things in the code decide how:
    • _off_the_tracking_branch reads only family.root (:2326), and _landing (:2463) has no Family, only into / family (and family there is the name string), so the helper takes the holder path, or the clause is built beside it.
    • The <Project> placeholder cannot go through _switch_command, which quotes its branch word: _print_next_steps writes the placeholder after the quoted line, bare (:3497, :3513, {switch} family/add-<Project>), because "quoted, it would read as a value" (_family_line's docstring, :2291-2292). The clause for (c) names family/add-<Project> as the NEXT STEPS block does, or a plain word; which one is the owner's call.
    • tests/test_readme_windows_quoting_sentence.py counts the host-dialect quoter calls per function (NAMED, :59; _print_next_steps 2, _switch_command 1, _add_command 1, _family_line 3). A plain branch word goes through _switch_command and adds no call to those counts. The bare placeholder needs a shell_line call of its own, in a function NAMED does not list (_landing, or a new helper), and that fails the file until its table and the README and handbook sentence say so (switch -c is already a kind they list). Read, not measured.

No remedy text appears in README.md, docs/handbook.html, AGENTS.md or docs/*.md (the review grepped them), so the change should stay in scripts/family.py and the tests. Grep tests/ for the remedy's words before changing a sentence.

Related items (the review's NITs)

  • NIT-1: kill mutant R5. member-already-present builds its branch from the row's id: (_suggested_branch("bump", str(held.get("id") or name)), :6542), and every fixture row's id is its project lowercased, so a mutant that builds it from the name passes. In test_a_rows_name_with_a_space_is_one_word_in_both_lines (tests/test_family_remedy_lines.py:318), also assert the switch -c line. That row's project is IR RS and its id is irrs: the id gives family/bump-irrs, the name gives family/bump-member. The file has the helpers (switch_line, used at :131, and OFF_TRACKING, :74). The mutant is harmless in effect, since any branch but the tracking branch works; the assert pins the choice.
  • NIT-2: the rows do not run the printed interpreter. typed() (tests/test_family_remedy_lines.py:152-157) asserts words[0] == PYTHON and runs the script by sys.executable (run_script). The review found no defect (12 of 12 typed through real shells), only a gap in what the rows prove. Optional: one row that looks words[0] up on the PATH the test gives (shutil.which) and runs the line by it, or a sentence in typed()'s docstring saying it does not. Closing it as "documented" is a fair answer.
  • NIT-3: remove lands on a branch named bump. member-already-present suggests family/bump-<id> for both of its lines, so a remove made from that branch is on a branch with bump in its name, where remove's own refusal would suggest family/remove-<id>. Harmless. Leave it, or mention it; it is the same question as which suggested the (a) clause takes, since (a) also names two verbs.

Verify

Found by lane 5's independent review of #1201 (#1127), SHOULD-1 and NIT-1 to NIT-3 (#1201 issuecomment-6372152028), at eac177af9; the code facts were re-read in scripts/family.py and the tests at that head, and the review's probe logs for p6, p7 and p9 were re-read for the answers quoted.

Lane: openreposhape-5 (openRepoShape-5)

Activity

  1. brettheap commented on Oct 23, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-5, session 50cede29-ab82-4ca3-9f67-d98e1a58c8d2@Eagle, 2026-10-23T21:19:53Z, for #1228

    Logged in lanes/log/openRepoShape-5.md at 60d075a014dffe90512fca89d9414571e995c65c.

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

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

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions