Skip to content

validate.yml (assembly-root and family-root templates): each submodule url is read by its exact key, --name-only then --get, so a section named members/My IRRS gives the url git gives, where the split at the first space gave IRRS.url <url>, left that repository out of the App token and listed it wrong in both failure steps (#741) - #782

Merged
brettheap merged 3 commits into
mainfrom
l3-741-template-urls
Oct 17, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-3 (openRepoShape-3)
Claim: #741 (comment)

Closes #741.

Lands after the v1.1.0 tag. Draft until then.

What changed and why

Both validate.yml templates read .gitmodules three times, and each read split git config --get-regexp '\.url$''s key value lines at the first space (| cut -d' ' -f2-, | while read -r _ url). A section name may hold a space, so for members/My IRRS the url came out as IRRS.url <url>. The names step matched that to neither https://github.com/ nor git@github.com:, so it left the repository out of the App-minted token without a word, and the fetch step and the failing step printed the wrong url. #700 (55c6ba7) made the doctor read such a name; CI now agrees with it.

The lines the issue names hold on main bf1f099, at the same numbers: assembly-root :87, :173, :220, family-root :83, :174, :214.

Now each url is read by its exact key: git config --file .gitmodules --name-only --get-regexp '\.url$' lists the keys one per line, and url=$(git config --file .gitmodules --get -- "$key") asks for each. Changed, with line numbers in the new files:

  • templates/assembly-root/.github/workflows/validate.yml: the names step :79-83, :94 and :103 (legs), the fetch step's list :189-193, the failing step's list :240-244; the step comment :60-69 says why.
  • templates/family-root/.github/workflows/validate.yml: the names step :75-79, :90 and :99 (members), the fetch step's list :190-194, the failing step's list :234-238; the step comment :56-65 says why.
  • tests/test_validate_workflow_gitmodules_urls.py (new).

Which of the issue's two fixes, and why

The second: list the keys, then ask for each url by its exact key.

  • A key holds no line break (git refuses one: error: invalid key (newline)), so one key per line is exact. IFS= read -r keeps a space, a tab, #, ; and a leading - in a name whole, and --get hands back the url as git reads it, whatever bytes it holds.
  • --get-regexp '\.url$' lists the url key of ANY section, not only submodule.*, so a key can begin with - ([-x] url = ... gives -x.url). --get -- "$key" ends the options there; without --, git reads -x as a switch, prints its usage dump and exits 129. (Round 1 said every key starts submodule.; that was wrong, see Round 2.)
  • It is POSIX shell. The two printing steps are POSIX today and stay so; the names step keeps its bash < <(...). The first fix needs read -d '' and $'\n', which are bash, in steps that are not.
  • A key with no value is, with -z, a record with no newline to split at: the first fix needs a case for it. Here --get answers an empty url, which the names step skips as it did before and the printing steps list as an empty line, as before.
  • It reads a section given twice as git does: --get returns the last value.

Round 2 (RV-c's review of 1bb59b6: READY, two SHOULDs and a NIT folded in)

  • SHOULD 1, a url holding a line break. A url with a newline (git's \n escape inside quotes), for example https://github.com/acme/A.git\n::error title=x::injected, made round 1 write legs=A.git and then ::error title=x::injected into $GITHUB_OUTPUT, and print ::error title=x::injected as a line of its own, which a runner reads as a workflow command. Main prefixed every printed line with ::warning:: . Now each of the six sites skips a url holding a newline or a carriage return right after it is read (case $url in *'<newline>'*|*"$(printf '\r')"*) continue ;; esac; the second line of the pattern starts at the script's left edge, which is the block's indent in the YAML). git cannot clone such a url. Assembly root :81-83, :191-193, :242-244; family root :77-79, :192-194, :236-238.
  • SHOULD 2, each repository once. The names step checks case ",$legs," in *",$name,"*) continue ;; esac before each append ($members in the family root): assembly :94, family :90. Round 1 gave legs=A,A for a url given twice in one section (stale, then real), A,B,A for a section reopened further down, and A,A for two paths mounting one repository. The pinned actions/create-github-app-token sends the list to GitHub's token API as it is.
  • NIT, --get -- "$key": the six sites above (the url=$(...) lines).
  • Not changed, as RV-c and the coordinator ruled: SHOULD 3, the sections git ignores ([submodule ""], a url with no path, a [remote "origin"] url named as a repository), which main reads the same. Lane 3 files it as its own issue. IFS= on the key reads stays, harmless.

Pinned copies

Both files are pinned template copies, carried by digest (scripts/shape_materialize.py:455 and :539). Each project and each holder that carries the shape sees one upstream-changed row for .github/workflows/validate.yml until it takes the new copy with update-shape.py.

Tested

tests/test_validate_workflow_gitmodules_urls.py runs each of the three steps' run: bodies unmodified, as real bash (bash -e, as the runner starts a step), in both templates, over a .gitmodules that git wrote. Its section names hold a space (members/My IRRS), a tab, and a leading - with # and ;; one url holds the same bytes (a leading -, a space, a tab, #, ;, a trailing space); two urls hold a newline and a carriage return, each followed by a ::error command. The one thing a runner does that a test cannot, reach the legs, is a git stub that exits 1 on submodule, which is how the two printing steps are reached; every other git is the real one. Each step is asserted to name or list exactly what the file holds, with the line-break urls skipped, and:

  • no line a step prints starts with :: but the step's own, and the names step's $GITHUB_OUTPUT holds only key=value lines (each of the three steps, both templates);
  • the names step names a repository once for a url given twice in one section, a section reopened further down, and two paths mounting one repository (both templates);
  • each step run over a [remote "origin"] url key and a [-x] url key prints nothing on stderr (both templates);
  • no --get-regexp is left without --name-only, so the class of defect cannot come back in a fourth read.

Fail first, measured. On the first-space split, all eight round-1 tests fail (legs=Tabbed where legs=IRRS,Tabbed,Odd; ::warning:: IRRS.url https://github.com/acme/IRRS.git where ::warning:: https://github.com/acme/IRRS.git). On round 1's templates, 26 of the 32 tests fail. With a site left without --, without the line-break skip, without its carriage-return half, or without the duplicate check, 6, 14, 14 and 6 fail.

env PATH="$S/stub:$PATH" GH_TOKEN=bogus GH_CONFIG_DIR="$S/ghcfg" /usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=$S/bt -o tmp_path_retention_policy=failed tests/test_validate_workflow_gitmodules_urls.py tests/test_scaffold_e2e.py tests/test_family.py tests/test_template_values_single_pass.py tests/test_repo_hygiene.py tests/test_cli_reference.py tests/test_scaffold_pin_and_reuse.py tests/test_windows_paths.py tests/test_validate_pins_unreadable_workflow.py tests/test_validate_pins_unreadable_workflows_dir.py
616 passed in 99.97s (0:01:39)

($S/stub/gh exits 97.) actionlint 1.7.12 with shellcheck is clean on both templates, after round 2 as before.

The new tests are WINDOWS_SKIP like test_the_leg_name_extraction_... beside them: they run the steps as the real bash they are, and CI is Linux only.

No documentation states how these steps read a url, so README.md, AGENTS.md, docs/cli.md and the handbook are not touched.

Overlap

gh pr list -R opensoft/openRepoShape --state open (29 PRs, files read): none touches a validate.yml template.

🤖 Generated with Claude Code

Summary by Sourcery

Fix validation workflows to preserve exact .gitmodules URLs and safely generate complete, deduplicated repository scopes and diagnostics.

Bug Fixes:

  • Read submodule URLs by their exact .gitmodules keys so repository names containing spaces, tabs, or leading option-like characters are handled correctly in token scoping and failure messages.
  • Skip URLs containing newlines or carriage returns to prevent unintended workflow commands from being emitted.
  • Avoid duplicate repository names when generating the App token scope.

Enhancements:

  • Apply consistent URL extraction and reporting across assembly-root and family-root validation workflows.

Tests:

  • Add workflow-level tests covering unusual section names and URLs, unsafe line breaks, duplicate repositories, non-submodule URL keys, and all affected validation steps.

…e url is read by its exact key, `--name-only` then `--get`, so a section named `members/My IRRS` gives the url git gives, where the split at the first space gave `IRRS.url <url>`, left that repository out of the App token and listed it wrong in both failure steps (#741)

The templates read `.gitmodules` three times each, and each read split
`git config --get-regexp '\.url$'`'s `key value` lines at the first space
(`| cut -d' ' -f2-`, `| while read -r _ url`). A section name may hold a
space, so for `members/My IRRS` the url came out as `IRRS.url <url>`: the
names step matched it to neither `https://github.com/` nor `git@github.com:`
and left the repository out of the App-minted token, and the fetch step and the
failing step printed it wrong. #700 made the doctor read such a name; CI now
agrees with it.

Now `--name-only --get-regexp` lists the keys, one per line (git refuses a
newline in a key), and `git config --get "$key"` asks for each url, as git
reads it: the last value of a section given twice, a url holding a space, a
tab, `#`, `;` or a leading `-` whole. That is POSIX shell, so the two printing
steps stay what they were, and the names step keeps its bash `< <(...)`.

The issue's other way, `-z` with each record split at its first newline, was
not taken: it needs `read -d ''` and `$'\n'`, which are bash, in two steps that
are POSIX shell today, and a key with no value is a record with no newline to
split at.

tests/test_validate_workflow_gitmodules_urls.py runs each of the three steps'
`run:` bodies unmodified, in both templates, over a `.gitmodules` whose section
names hold a space, a tab, and a leading `-` with `#` and `;`, and whose one
url holds the same bytes; all eight tests fail on the old templates.

Both files are pinned template copies: each project and each holder sees one
`upstream-changed` row for `.github/workflows/validate.yml`.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fix both validation workflow templates so .gitmodules entries with spaces or other special characters are read by exact key rather than split key value output, preventing repositories from being omitted from App tokens and ensuring failure diagnostics show the correct URLs. Add end-to-end shell-level regression tests covering all three read paths in both templates.

Sequence diagram for exact .gitmodules URL lookup in validation workflows

sequenceDiagram
    participant Workflow
    participant Git
    participant NamesStep
    participant SubmoduleStep
    participant FailureStep

    Workflow->>Git: git config --file .gitmodules --name-only --get-regexp \.url$
    Git-->>NamesStep: Exact submodule keys, one per line
    loop Each key
        NamesStep->>Git: git config --file .gitmodules --get key
        Git-->>NamesStep: URL for exact key
    end
    NamesStep->>Workflow: legs or members output for App token

    Workflow->>SubmoduleStep: git submodule update --init --recursive
    alt Checkout fails
        SubmoduleStep->>Git: git config --file .gitmodules --name-only --get-regexp \.url$
        loop Each key
            Git-->>SubmoduleStep: Exact submodule key
            SubmoduleStep->>Git: git config --file .gitmodules --get key
            Git-->>SubmoduleStep: URL for exact key
        end
        SubmoduleStep->>Workflow: Warning with correct URLs
        Workflow->>FailureStep: Report unavailable submodules
        FailureStep->>Git: git config --file .gitmodules --name-only --get-regexp \.url$
        loop Each key
            Git-->>FailureStep: Exact submodule key
            FailureStep->>Git: git config --file .gitmodules --get key
            Git-->>FailureStep: URL for exact key
        end
        FailureStep-->>Workflow: Error with correct URLs
    end
Loading

File-Level Changes

Change Details Files
Replace whitespace-unsafe .gitmodules URL parsing with exact-key lookups in both validation workflow templates.
  • List .url keys with --name-only, then retrieve each value with git config --get.
  • Apply the same lookup logic to App-token repository-name extraction and both URL-reporting failure paths.
  • Document why exact-key reads are required and preserve POSIX-shell compatibility in printing steps.
templates/assembly-root/.github/workflows/validate.yml
templates/family-root/.github/workflows/validate.yml
Add regression coverage for section names and URL values containing shell- and whitespace-sensitive characters.
  • Execute all affected workflow run: bodies unmodified under bash for both templates.
  • Verify repository-name extraction and warning/error URL listings preserve Git’s exact values.
  • Guard against future --get-regexp consumers that omit --name-only.
tests/test_validate_workflow_gitmodules_urls.py

Assessment against linked issues

Issue Objective Addressed Explanation
#741 Read each submodule URL in both validate.yml templates according to Git’s parsing, so section names containing spaces do not corrupt the URL. ✅
#741 Apply the corrected URL reading to all three workflow uses in each template: repository names for the App token, and URL listings in both failure paths. ✅
#741 Add coverage in both templates for section names containing spaces, exercising the steps that consume or display the URLs. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-17T04:25:34Z) Independent review of #782 by RV-c (opus), on head 1bb59b6a: READY. Nothing blocks it. Two SHOULDs are worth folding in; a third becomes a follow-up issue. It lands only after the v1.1.0 tag, on a green Linux tests (still queued at 04:24Z).

Measured. RV-c ran the three steps of both templates against 21 real .gitmodules, each compared with git submodule init. Every one of these now gives exactly git's url and App-token list:

  • a space, a tab, a trailing space;
  • #, ;, =, a leading -;
  • a quote, a backslash;
  • a Latin-1 byte, a UTF-8 name;
  • an upper-case key.

On main, the space, tab and trailing-space names gave IRRS.url … or .url …, and the names step dropped the repository from the token.

SHOULD 1: a url holding a newline breaks the names step and lets that url print its own workflow command line. This is new against main.

  • Where:
    • assembly root: validate.yml:77, :183, :231;
    • family root: :73, :184, :225.
  • Scenario: url = "https://github.com/acme/A.git\n::error title=x::injected", using git's \n escape.
  • Main: wrote legs=A, and prefixed every printed line with ::warning:: .
  • The head:
    • writes legs=A.git and then ::error title=x::injected into $GITHUB_OUTPUT, so the runner rejects that line and the step fails unclearly;
    • the fetch and failing steps print ::error title=x::injected as its own line, which the runner reads as a command.
  • Impact: low. The triggers are pull_request and workflow_dispatch only, and git cannot clone such a url.
  • Fix: right after each url=$(…), skip a url holding a newline or a CR, and add one test row.

SHOULD 2: deduplicate legs and members.

case main head
one section gives its url twice (stale, then real) legs=Stale,A legs=A,A
a section reopened further down Stale,B,A A,B,A
two paths mount one repository A,A A,A

The pinned actions/create-github-app-token (fee1f7d6, main.js:21-23) sends the duplicates to GitHub's token API. RV-c could not measure how the API treats them.

  • Fix: case ",$legs," in *",$name,"*) continue ;; esac before each append ($members in the family root). This also settles the writer's "not settled" point.

SHOULD 3 (follow-up issue, not this PR's): CI still reads sections that git, and the doctor after #775, ignore. Main behaves the same. None of the following gets a url from git submodule init, yet CI still names it in the token, lists it, or both:

  • [submodule ""] and [submodule "../a"] are named in the token;
  • a url with no path names Nowhere;
  • a [remote "origin"] url inside .gitmodules names Other;
  • a bare url elsewhere makes git refuse the file, yet CI lists A plus an empty line;
  • url = -oProxyCommand=x is listed.

Where the extra repository is one the App cannot reach, the mint step fails the job. Lane 3 files it.

NITs:

  • "Every key starts submodule." is not so. --get-regexp '\.url$' lists any section's url, and [-x] url = … gives git config --get '-x.url', which exits 129 with a usage dump. Only || continue skips it, and that mutant survives. Use --get -- "$key" (measured: exit 0), and add a non-submodule section row.
  • No test row has a non-ASCII name or a repeated url. Both behave correctly today.
  • IFS= on the key reads is not needed. Harmless.

Checked:

  • The issue's six lines are at :87, :173, :220 and :83, :174, :214 on bf1f099b. Main fails all 8 new tests.
  • The writer's other claims hold.
  • actionlint with shellcheck is clean.
  • The locale cannot matter: a Latin-1 name is listed under C.UTF-8.
  • --name-only arrived in git 2.6.0 (2015). The templates already need 2.23 for git switch, and git 2.55's source still accepts this form (builtin/config.c:1427-1428).
  • Both files are listed in scripts/shape_materialize.py:455 and :539, and the body names the upstream-changed row.
  • Docs, Windows and Sonar are clear.

Mutants:

mutant result
main's templates 8 failed
a site reverted 2 failed
--name-only dropped 9 failed
"$key" unquoted 6 failed
IFS= dropped survives, harmless
|| continue dropped survives (the NIT)

Tests: 9 files on the head: 546 passed.

Merges: clean with every open lane-3 PR at its current head. No other open PR, out of 30 in any lane, touches either template.

Lane: openreposhape-3 (openRepoShape-3)

… a line break or a carriage return is skipped, each repository is named once, and each url is read with `--get --`, so a url cannot print a workflow command of its own, `legs` and `members` hold no repository twice, and `[-x] url` is read without git's usage dump (#741)

Round 2 of #782, from RV-c's review.

- A url holding a newline (git's `\n` escape inside quotes) or a carriage
  return is skipped right after it is read, at all six sites. Main prefixed
  every printed line with `::warning::  `; the first round wrote that url's
  second line to `$GITHUB_OUTPUT` and printed it as a line of its own, which a
  runner reads as a command (`::error title=x::injected`). git cannot clone
  such a url.
- The names step names a repository once: `case ",$legs," in *",$name,"*)
  continue ;; esac` before each append (`$members` in the family root). The
  first round gave `legs=A,A` for a url given twice in one section, a section
  reopened further down, and two paths mounting one repository; the pinned
  create-github-app-token sends the list to GitHub's token API as it is.
- `--get -- "$key"`. `--get-regexp '\.url$'` lists any section's url key, not
  only `submodule.*`, and `[-x] url = ...` makes `git config --get -x.url` read
  `-x` as a switch: a usage dump on stderr, exit 129, and the `|| continue`
  swallowing it. The first round's note that every key starts `submodule.` was
  wrong.

tests/test_validate_workflow_gitmodules_urls.py: the shared `.gitmodules` holds
a newline row and a carriage-return row, and each of the three steps in both
templates prints no `::` line but its own and writes only `key=value` lines to
`$GITHUB_OUTPUT`; the names step is run over the three duplicate cases; and each
step is run over a `[remote "origin"]` and a `[-x]` url key with stderr asserted
empty. Against the first round's templates 26 of the 32 tests fail; a site
without `--`, without the line-break skip (or its carriage-return half), or
without the duplicate check fails 6, 14, 14 and 6.

Left for a follow-up issue, as RV-c asked: sections git ignores (`[submodule
""]`, a url with no path, a `[remote]` url named as a repository), which main
reads too.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-17T04:36:36Z) RV-c's re-verification of #782 round 2 (67324e91): READY. Both SHOULDs and the NIT are fixed, each measured. It lands only after the v1.1.0 tag, on a green Linux tests (pending at 04:36Z; Sonar, package and tests-git-bash pass).

SHOULD 1 (a url holding \n or \r) is fixed in all six places.

  • Round 1: printed ::error title=x::injected and ::error::cr as raw lines, and wrote the injected line into $GITHUB_OUTPUT.
  • Head: prints nothing for that url, and $GITHUB_OUTPUT holds only legs=.

SHOULD 2 (duplicates) is fixed:

case round 1 head
one section gives its url twice legs=A,A legs=A
a section reopened further down A,B,A A,B
two paths mount one repository A,A A

First-seen order is kept. A repeated foreign-owner repository still warns once per occurrence, which is acceptable.

The NIT is fixed. --get -- "$key" in all six reads means a [-x] section no longer gives exit 129 and a usage dump. Its url is now read, so Dash joins the token, as [remote "origin"] adds Other on main. Both belong to #786.

The silent skip is acceptable. git cannot clone such a url, so leaving it out of the token changes nothing. Optionally, the printing steps could name the section, stripping CRs from the key first, since --name-only prints submodule.a\rb.url raw.

The two-line pattern survives rendering byte for byte.

  • RV-c rendered both templates through the suite's project and holder fixtures. Each rendered validate.yml is identical to its template, since it carries no {{KEY}} and is COPIED_VERBATIM.
  • PyYAML parses all six run: strings as the tests extract them.
  • A re-indent mutant (two spaces on the pattern's second line) leaves actionlint silent but fails 3 tests. The tests pin the behaviour, not just the bytes.

Tests and lint:

  • 10 files: 616 passed;
  • actionlint 1.7.12 with shellcheck: clean.

Merges: clean with every open lane-3 PR at its current head. No other open PR touches either template.

Lane: openreposhape-3 (openRepoShape-3)

@sonarqubecloud

Copy link
Copy Markdown

@brettheap
brettheap marked this pull request as ready for review October 17, 2026 22:19
@sourcery-ai

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 hours and 3 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@brettheap
brettheap merged commit a9d1579 into main Oct 17, 2026
5 checks passed
@brettheap
brettheap deleted the l3-741-template-urls branch October 17, 2026 22:20
brettheap pushed a commit that referenced this pull request Oct 17, 2026
…parent this branch was stacked on, as it landed (#786)

#782 (#741) landed as squash a9d1579, whose tree is a59cb53's (`git diff
--stat a59cb53 a9d1579` is empty). a59cb53 is #782's last merge of main,
so this brings in main as #782 landed on it, #630's v1.1.0 docs (190133d)
among it.

The one conflict is docs/handbook.html, on the three `§ The three legs`
markers (masthead, `ci`, `legs`): main's 1d9f5bf9, from #630's reading,
against this branch's a1299381, from #790's. README.md merged clean with both
sides standing: #630's text, and #790's sentence in § Reading private legs in
CI. All three markers are set to 4ec313e8424665e41fc92a4147808e0bd4901d78,
the blob of the merged README's § The three legs as `readme_segments` and
`readme_fingerprint` in tests/test_repo_hygiene.py compute it (`git
hash-object --no-filters` of the segment agrees). The page already says what
each side changed in that segment, #630's in its own sections and #790's
sentence in `ci`, so moving the markers is the whole of the change here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 17, 2026
…main holds it, its three files taken from this branch (#786)

a9d1579 is #782 (#741) squashed onto main; its tree is that of a59cb53,
merged in the commit before this one, so this merge changes no file. It
conflicts in the three files #782 changed, both templates'
`.github/workflows/validate.yml` and tests/test_validate_workflow_gitmodules_urls.py,
because the squash carries #782's lines as new ones; each takes this
branch's side, which is #782's final content with #790's own changes on top.
The tree is the parent's, 9f07f48.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 17, 2026
…anded, among it #805's `-z` read of `.gitmodules` in siblings.py (#786)

A clean merge. It brings in what main took after #782's squash, among it
#805 (#690, 40e678e), which reads `.gitmodules` in the family template's
siblings.py with `git config -z --list`. README.md stays 1475 lines, at
the cap of 1475 that tests/readme-cap.d/ gives with this branch's 786.txt
(+1); AGENTS.md is main's, 494 lines at its cap of 494; and every
handbook marker is current. The tree is 1d73091, what one merge of main
into 31c3792 gives with the same resolutions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant