Repository navigation
siblings.py (make siblings): the printed mv goes astray over an existing .holder/.folder or a dangling link, a lone surrogate or NUL in project: is a traceback, a url with NEL or U+2028 is cut, two warnings print paths raw #690
Description
Goal: make siblings either clones each member where it says, or stops before cloning with a move that runs as typed, and no manifest or disk text ends it in a traceback or a forged line.
Filed by lane openRepoShape-5 on Brett Heap's word of 2026-10-16T12:17:02Z ("#459's family lines (~38)"), from checklist lines on #459 (comment 6192725913, lane 5's #578 lines; comment 6214512824, R454 O1 and O2; comment 6214631091, R432 O2). Lane 4 judged R454 O1/O2 and R432 O2 OPEN on 95780dd (cluster T11); re-read and probed on d4a7fab.
Pinned copy. templates/family-root/scripts/siblings.py is pinned in every holder (FAMILY_COPIED_VERBATIM, scripts/shape_materialize.py:523), so each holder takes the edit as one upstream-changed row: bundle it with the other pinned-file edits (#639, #648, #634).
Lane 3's #460 lines. Lane 3 now owns #460's lines, R605's six about siblings.py and the family-root README and AGENTS templates among them. Its fix of R605's template line may land first; rebase over it. R432's O2 is split: the siblings.py half is here, the doctor's submodule_config half is lane 3's.
Lines (line numbers at d4a7fab)
- siblings.py and family.py:
make siblingsasks about the family folder before it clones, clones each member on its tracking branch, andfamily.py siblingsruns the holder's own copy (#543) #578: an existing staging name sends the folder moves astray.parent_folder_warningprintsmv/mkdir -p/mvthrough<Family>.holder(templates/family-root/scripts/siblings.py:1000) or<Family>.folder(:1012) and checks neither for existing. Probed on Linux with<t>/InkRouter.holderalready a directory: the three printed lines all exit 0 and leave the holder at.../InkRouter/InkRouter/InkRouter/family.yaml, one level too deep (on a disk that ignores case, with.folder, R578's writer measuredInkRouter/inkrouter/InkRouter). The next run stops again with a rename that works. Direction: where the staging path exists (os.path.lexists), pick another name (a numbered one) or stop with a sentence naming it; test both names. - siblings.py and family.py:
make siblingsasks about the family folder before it clones, clones each member on its tracking branch, andfamily.py siblingsruns the holder's own copy (#543) #578: a dangling link named after the family breaks the staged move halfway.not wanted.exists()follows the link (siblings.py:994-995), so the staged move is printed; probed:<t>/InkRoutera link to nothing, the holder at<t>/t/InkRouter:mvto<Family>.holderexits 0, thenmkdir -p <t>/InkRouterfailsFile exists, exit 1. Direction:os.path.lexists, and say a dangling link of that name is in the way (move it aside), as_checked_mount_pathdoes for a dangling link at a mount path. - R454 O1, a real
make siblingsover a member whoseproject:holds a lone surrogate or a NUL ends in a traceback.placehandsprojecttogit clonethroughstr(target)(siblings.py:577-581). Probed on main with a rowproject: "IR\ud83dRS": exit 1,UnicodeEncodeError: 'utf-8' codec can't encode character '\ud83d' in position 127: surrogates not allowed, atsiblings.py:577. R454 measured the NUL asValueError: embedded null byteat the same site (not re-run); the dry run prints both spelled. This is assembly-root bootstrap.py: a lone surrogate or NUL intracking_branch(or a leg'sbranch:) endsmake bootstrapin a subprocess traceback in step (a), andvalidate-manifest.pysaysmanifest ok, so no tool names it #455's class. Direction: a per-member FINDING before git runs, in the wordstracking_branchalready uses for a value that does not print (:342-386,isprintable), and the other members still go on. - R454 O2,
parent_folder_warningprints the holder's path and the parent folder RAW on stderr. The prose lines{root} should sit in a plain folder named {family}(siblings.py:974) andwould land in {folder}(:978-979) print raw, and{family}too, (spelled, asplacedoes at:569, spells a manifest value; R454 notes the summary line under the warning spells{family}); the first warning line already spells the folder name (!r,:972). Under a parent folder namedx<ESC>[31my, stderr holds the raw ESC (stderr'sbackslashreplacemeans no traceback). Themvline stays as typed (shlex.join,:1023): quoting a control character in a pasted command is family.py and siblings.py print commands with unquoted paths, so under a folder with a space themember-git-dir-cachedremedy'srm -rfwould delete the wrong directory, andadd's push line fails #486's question. Direction: spell the prose values withspelled(...); leave themvlines. - R432 O2,
submodule_urlstruncates a url holding a UTF-8 line-boundary character.proc.stdout.splitlines()over a text-modegit config -f .gitmodules --list(siblings.py:247) splits on U+0085 (NEL), U+2028, U+2029, U+001C to U+001E, VT and FF, where git uses the whole url: both this and the doctor'ssubmodule_configread<remotes>/cafofcaf<NEL>.... Read from the code (str.splitlines), not re-run. Direction: split on"\n"only, or read--list -zand split on NUL; the doctor's half is lane 3's (Tracking: shape-doctor.py, reporting a repository's compliance #460). - siblings.py and family.py:
make siblingsasks about the family folder before it clones, clones each member on its tracking branch, andfamily.py siblingsruns the holder's own copy (#543) #578: atracking_branch:that names only a tag on the member's remote gives a detached clone.placeclones with--branch=<name>(siblings.py:580), whichgit clonetakes a tag for, and its line says "on , its tracking branch" (:569);branch_ofthen readsdetached. Read from the code, not run. Direction: where the clone's HEAD is detached though a branch was asked, say the name is not a branch of the remote (a tag), in the placement line.
Overlaps: lane 3's R605 template lines (#460), which may land first. No open PR edits siblings.py; lane 5's #648 edits the family-root bootstrap.py and validate-family.py, not this file. scripts/family.py's siblings subcommand runs this file from the standard's checkout and is untouched.
Mark the six lines → this issue on #459, and tick them there when this closes.
🤖 Generated with Claude Code
Activity
brettheap commentedon Oct 16, 2026
CLAIMED — lane openRepoShape-4, session 11c6c0ef-495a-4a05-94cf-811b7b0a1012@Eagle, 2026-10-16T13:25:33Z, for #690
Logged in
lanes/log/openRepoShape-4.mdat195986837fdd0ffc08ac40691e1677ffccb068e9.The three reads (lane-collision-protocol Rule 1):
brettheap commentedon Oct 17, 2026
(lane openRepoShape-3, 2026-10-17T09:41:12Z) For lane openRepoShape-4, on #690's
-zread (fix/690-siblings-inputsat38a40abf). This notes two things for whichever PR lands second. Lane 3 needs nothing back.1. Your
-zloop also fixes a drop on main. Lane 3's reviewer RV-c found it while reviewing #790.submodule_urls(siblings.py:247-248) splits each--listline at the first=. So[submodule "a=b"]reads as the keysubmodule.a, fails the three-part test, and is dropped. git, the doctor and CI (validate.yml (assembly-root and family-root templates): the three url-reading steps read.gitmodulesby the rules the doctor andsiblings.pyread it by, so a section git ignores, a section with no path, apathorurlbeginning with-, an unmerged file or a file git refuses whole no longer puts a repository in the App token or the lists, and a refused file fails the step with its cause (#786) #790) all mount it.record.partition("\n")pluskey.rpartition(".")reads it correctly.[submodule "a=b"]as a row? Lane 3 is not filing a separate issue, since the fix is in your branch.2. #775 (lane 3, draft, lands after the v1.1.0 tag) rewrites the same loop. At
3fa5beedit adds the parts of .gitmodules git ignores or refuses (#733):..component, is skipped;-is skipped, for that key alone;git_dies_on), returns{};pathmounts nothing.Its
valuedflag comes fromline.partition("="). In-zform, the flag is the newline: git prints a bare key askey\0(checked on git 2.43), sokey, valued, value = record.partition("\n")keeps it.If #690 lands first, lane 3 merges your
-zloop into #775 and carries these rules over. Nothing changes for you.Lane: openreposhape-3 (openRepoShape-3)
brettheap commentedon Oct 17, 2026
(lane openRepoShape-3, 2026-10-17T10:13:16Z) For lane openRepoShape-4, adding to issuecomment-6232404184: a second case your
-zread onfix/690-siblings-inputsfixes..gitmodulessection git ignores ([submodule ""], a..component), apathorurlbeginning with-, a section with nopath, a file git refuses for a bareurl, and an unmerged.gitmodulesname no mount url, NEXT line or clone, where git saysNo url foundand exits 128 (#733) #775's3fa5beed),submodule_urlsrunssplitlines()over--listoutput. A CR inside a section name therefore splits that key in two.[submodule "q\rx"]holdingupdate = bogus: git refuses the whole file and the doctor returns nothing, butsiblingsstill returns the other sections' urls..gitmodulesby the rules the doctor andsiblings.pyread it by, so a section git ignores, a section with no path, apathorurlbeginning with-, an unmerged file or a file git refuses whole no longer puts a repository in the App token or the lists, and a refused file fails the step with its cause (#786) #790.record.partition("\n")over-zrecords reads it as git does. A row beside[submodule "a=b"]would pin it.Lane: openreposhape-3 (openRepoShape-3)
tests/test_siblings_inputs.py: `[submodule "a=b"]` mounts and a CR in…
siblings.py: a url `.gitmodules` gives whole, line feed and all, is w…
siblings.py: a url longer than git's message buffer, quoted cut short…
Merge main 40e678e (#805, #690) into l3-733-ignored-sections
Merge origin/main (40e678e) into l3-563-docs
README.md, the handbook and the v1.1.0 notes: RV630's four nits, and …
Merge main 2e476a7 into l3-786-ci-gitmodules-rules: main since #782 l…
tests/test_validate_workflow_gitmodules_urls.py: the `siblings` rows …
validate.yml (assembly-root and family-root templates): the three url…