Skip to content

openRepoShape: $OPENREPOSHAPE_REPO and $OPENREPOSHAPE_REF are checked once, before --version answers and before any fetch, so a control character or a character a terminal draws as another in either is refused, never printed or sent - #872

Merged
brettheap merged 3 commits into
mainfrom
fix/866-launcher-repo-ref
Oct 18, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-2 (openRepoShape-2)
Claim: #866 (comment)

Base

Not stacked. The branch starts at origin/main 5cbec222, which holds #841 (ca47b71d). Two commits: d183e769, the change, and a2af74a0, fix round 1 on rv866's review.

Lands beside #867 (#858), lane 2's, in the same file: whichever lands second merges the other. At a2af74a0, git merge-tree --write-tree is clean with main d6475f96, with #867's head 8bb85cbf, and with the two together. At d183e769 and #867's earlier head 2d1ff8a1, the predicted tree 0d66e9e8 gave 645 passed in test_openreposhape_command.py and test_install_sh.py, #867's cases and these together. Nothing #867 adds is touched here: not locale_reading, not the bin-dir check, not misdrawn_characters, and the new tests are one block after the CLONE_URLS test, away from #867's hunks.

Fix round 1, on rv866's review (READY, 2 SHOULDs, 2 NITs)

The review: #872 (comment). One commit, a2af74a0.

Finding What changed
SHOULD-1: a URL, an scp address or a path in $OPENREPOSHAPE_REPO ending in a newline passed, was printed by --version and sent by a scaffold run checked_repo looked at url="$(shape_clone_url)", and $(…) drops trailing newlines. It now looks for the misdrawn characters in $REPO as typed, and its case reads "$url$REPO": a control character in either is refused, and -* still reads the URL's first character, so -acme/r (cloned as https://github.com/-acme/r.git) passes as before. rv866's probed fix, two lines; the words are unchanged. New cases: the path, URL and scp forms with a trailing newline through --version, the scaffold run and --install (18), and -acme/r printed by --version (2). The one-line revert (F01) fails the 18; rv866's R07 (url="$REPO") fails the 2
SHOULD-2: under C, every run but --help now refuses a URL or path holding a character past ASCII No code change: rv866 rules it out of scope. Stated under "Not changed", with the four runs measured again here
NIT-1: the order refuse_bad_values states is not pinned test_bad_values_are_refused_in_the_order_the_check_states: a broken record and a bad repository give the record's refusal (kills rv866's R03); a bad repository and a bad ref give the repository's (kills R04). One row cannot kill both: the record is read only where $OPENREPOSHAPE_REF is unset, so a broken record and a bad ref never both apply
NIT-2: a bare run with a bad value exits 2 with the refusal, not 1 with the usage No change; stated under "Not changed"

SHOULD-1, measured (LC_ALL=C.UTF-8, the same stubs; <LF> is a line feed):

d183e769:
$ OPENREPOSHAPE_REPO=/srv/r<LF> openRepoShape --version
  exit 0, stdout: openRepoShape (/srv/r<LF> @ main)
$ OPENREPOSHAPE_REPO=https://example.invalid/r<LF><LF> openRepoShape Atlas --org acme
  exit 2, 2 requests, each carrying both line feeds; the refusal prints them three times
$ OPENREPOSHAPE_REPO=git@example.invalid:r<LF> openRepoShape --version
  exit 0, stdout: openRepoShape (git@example.invalid:r<LF> @ main)
$ OPENREPOSHAPE_REPO=acme/r<LF> openRepoShape --version
  exit 2 (refused already: the owner/name keeps the line feed inside its URL)

a2af74a0: each of the four exits 2 with 0 requests:
  REFUSED: OPENREPOSHAPE_REPO is not a value this tool will put on a `git clone` command line: it starts with '-' or carries a control character. Set it to a URL or a path.
$ OPENREPOSHAPE_REPO=-acme/r openRepoShape --version
  exit 0, stdout: openRepoShape (-acme/r @ main)     (as at d183e769)

Decisions

  1. Where the one check runs. --version is an argument case (openRepoShape:1003 at 5cbec222). It answers inside the argument loop with its own refuse_bad_record, before the top-level call (:1112), so the check has to be in both places. One function, refuse_bad_values, now does three things: it refuses a broken record as before, checks the repository, then checks the ref. It replaces refuse_bad_record at both call sites, so the two cannot drift apart.
    • --help (:1002) still answers first. It names the variables and their defaults, never their values, so it answers past a bad value as it answers past a broken record.
    • A bare run now refuses a bad value with exit 2, as it already refuses a broken record. Before, it printed the usage and exited 1.
  2. The rules and the words.
    • $OPENREPOSHAPE_REPO: shape_checkout's guard, moved whole into checked_repo. install.sh and openRepoShape: a $OPENREPOSHAPE_BIN_DIR, the repository or a ref holding a bidi control, a character of no width, a soft hyphen or U+2028 is refused by its code point before any request and never echoed, so no line shows the value as another #841's misdrawn characters come first, then a leading - or [![:print:]]. The leading - is looked for in the URL that shape_clone_url makes; since fix round 1 the characters are looked for in $REPO as typed as well, because $(shape_clone_url) drops a trailing newline. The words are unchanged.
    • The forms $REPO takes are an owner/name (an API path for the fetch, and https://github.com/<o>/<n>.git for a clone), a URL or scp address, and a local path (/…, .…, C:/…, C:\…). None of them is refused unless it holds a control character or a misdrawn one. A leading - survives into the clone URL only when it is typed on a URL or an scp address, and setup.sh's clone and --doctor's clone refuse that already.
    • $OPENREPOSHAPE_REF: the character checks of ref_fault, not its length or shape check. Those two checks (the misdrawn characters, then [![:print:]]) move whole into ref_character_fault, which ref_fault calls in the same place, so ref_fault's order and words are unchanged.
    • Why not all of checked_ref: test_a_ref_set_without_the_opt_in_is_never_recorded pins that --install with OPENREPOSHAPE_REF=bad..ref and no $OPENREPOSHAPE_RECORD_REF exits 0. record_ref's comment gives the reason: workBenches sets a value "that names no ref at all, to make any fetch fail loudly". A ref of any shape or length prints as it reads, and --version "prints the ref in force" (docs/cli.md:168). The length and the shape are still checked wherever a ref is used: by checked_ref when --install records, by shape_checkout before git checkout, and by setup.sh's --shape-ref checks.
  3. --version refuses with exit 2. It does not respell the value.
  4. shape_checkout's own check is gone, moved into checked_repo. shape_checkout runs only from run_doctor, which runs only after the up-front check, and $REPO does not change in between. git clone --quiet -- "$url" keeps its --. install.sh and openRepoShape: a $OPENREPOSHAPE_BIN_DIR, the repository or a ref holding a bidi control, a character of no width, a soft hyphen or U+2028 is refused by its code point before any request and never echoed, so no line shows the value as another #841's a_repository_to_clone case still pins the words; it meets them up front now.
  5. Every line that prints or sends $REPO or $REF runs after the check. Those lines are in fetch_from_repo, fetch_or_die, collect_commands, record_ref, install_commands, shape_checkout, run_doctor and ref_fault. ref_fault's shape refusal comes only after its character checks pass. The others are the --version case, --shape-ref "$REF" and the OPENREPOSHAPE_REPO export. At a2af74a0, the top-level callers are run_doctor (:1186), install_commands (:1195), fetch_or_die (:1264) and the ARGS and export lines (:1252, :1271), all after refuse_bad_values (:1172). --version (:1062) calls it first. read_recorded_ref (:892) runs before the argument loop and prints nothing.

What changed

Before (5cbec222) and after, measured by a probe. The probe puts a gh stub and a curl stub first on PATH, each logging its call and failing; it sets GH_TOKEN=bogus and an empty GH_CONFIG_DIR. Bytes outside printable ASCII are shown as <U+XXXX>.

Before (5cbec222):

$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REPO=acme/r<U+202E>x openRepoShape --version
  exit 0, 0 requests
  stdout: openRepoShape (acme/r<U+202E>x @ main)
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REF=v1<U+001B>[31mX openRepoShape --version
  exit 0, 0 requests
  stdout: openRepoShape (opensoft/openRepoShape @ v1<U+001B>[31mX)
$ LC_ALL=C OPENREPOSHAPE_REPO=acme/r<U+009B>31mX openRepoShape Atlas --org acme
  exit 2, 2 requests
  stderr: REFUSED: could not fetch setup.sh from acme/r<U+009B>31mX at main. Both ways failed:
  stderr:     gh api repos/acme/r<U+009B>31mX/contents/setup.sh?ref=main
  stderr:     curl -fsSL https://raw.githubusercontent.com/acme/r<U+009B>31mX/main/setup.sh
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REF=v1<U+200B>x openRepoShape Atlas --org acme
  exit 2, 2 requests
  stderr: REFUSED: could not fetch setup.sh from opensoft/openRepoShape at v1<U+200B>x. Both ways failed:
  stderr:     gh api repos/opensoft/openRepoShape/contents/setup.sh?ref=v1<U+200B>x
  stderr:     curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/v1<U+200B>x/setup.sh
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REPO=acme/r<U+001B>[31mX openRepoShape --install (stdin)
  exit 2, 2 requests
  stderr: REFUSED: could not fetch openRepoShape from acme/r<U+001B>[31mX at main, so NOTHING was installed
  stderr:     and nothing already installed was replaced. `--install` places every one
  stderr:     of the 1 it collects or none of them: a new command beside a missing
  stderr:     one is a half-install that reads like a whole one. Both ways were tried:
  stderr:     gh api repos/acme/r<U+001B>[31mX/contents/<file>?ref=main
  stderr:     curl -fsSL https://raw.githubusercontent.com/acme/r<U+001B>[31mX/main/<file>
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REF=v1<U+202E>x openRepoShape --install (stdin)
  exit 2, 2 requests
  stderr: REFUSED: could not fetch openRepoShape from opensoft/openRepoShape at v1<U+202E>x, so NOTHING was installed
  stderr:     and nothing already installed was replaced. `--install` places every one
  stderr:     of the 1 it collects or none of them: a new command beside a missing
  stderr:     one is a half-install that reads like a whole one. Both ways were tried:
  stderr:     gh api repos/opensoft/openRepoShape/contents/<file>?ref=v1<U+202E>x
  stderr:     curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/v1<U+202E>x/<file>
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REPO=-x://acme/r openRepoShape --version
  exit 0, 0 requests
  stdout: openRepoShape (-x://acme/r @ main)
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REF=bad..ref openRepoShape --version
  exit 0, 0 requests
  stdout: openRepoShape (opensoft/openRepoShape @ bad..ref)
$ LC_ALL=C.UTF-8  openRepoShape --version
  exit 0, 0 requests
  stdout: openRepoShape (opensoft/openRepoShape @ main)

After (d183e769):

$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REPO=acme/r<U+202E>x openRepoShape --version
  exit 2, 0 requests
  stderr: REFUSED: OPENREPOSHAPE_REPO holds a character a terminal draws as something other than itself (U+202E): one that is invisible, is drawn as another character, ends a line or reorders the text around it. This tool will not put it on a `git clone` command line. Set it to a URL or a path.
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REF=v1<U+001B>[31mX openRepoShape --version
  exit 2, 0 requests
  stderr: REFUSED: $OPENREPOSHAPE_REF carries a control character, which is not a branch, tag or commit this tool will pass to `git checkout`.
$ LC_ALL=C OPENREPOSHAPE_REPO=acme/r<U+009B>31mX openRepoShape Atlas --org acme
  exit 2, 0 requests
  stderr: REFUSED: OPENREPOSHAPE_REPO is not a value this tool will put on a `git clone` command line: it starts with '-' or carries a control character. Set it to a URL or a path.
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REF=v1<U+200B>x openRepoShape Atlas --org acme
  exit 2, 0 requests
  stderr: REFUSED: $OPENREPOSHAPE_REF holds a character a terminal draws as something other than itself (U+200B): one that is invisible, is drawn as another character, ends a line or reorders the text around it. No branch, tag or commit this tool will pass to `git checkout` holds one.
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REPO=acme/r<U+001B>[31mX openRepoShape --install (stdin)
  exit 2, 0 requests
  stderr: REFUSED: OPENREPOSHAPE_REPO is not a value this tool will put on a `git clone` command line: it starts with '-' or carries a control character. Set it to a URL or a path.
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REF=v1<U+202E>x openRepoShape --install (stdin)
  exit 2, 0 requests
  stderr: REFUSED: $OPENREPOSHAPE_REF holds a character a terminal draws as something other than itself (U+202E): one that is invisible, is drawn as another character, ends a line or reorders the text around it. No branch, tag or commit this tool will pass to `git checkout` holds one.
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REPO=-x://acme/r openRepoShape --version
  exit 2, 0 requests
  stderr: REFUSED: OPENREPOSHAPE_REPO is not a value this tool will put on a `git clone` command line: it starts with '-' or carries a control character. Set it to a URL or a path.
$ LC_ALL=C.UTF-8 OPENREPOSHAPE_REF=bad..ref openRepoShape --version
  exit 0, 0 requests
  stdout: openRepoShape (opensoft/openRepoShape @ bad..ref)
$ LC_ALL=C.UTF-8  openRepoShape --version
  exit 0, 0 requests
  stdout: openRepoShape (opensoft/openRepoShape @ main)

No refusal's words changed. refuse_bad_values prints the words shape_checkout and ref_fault already had.

--version costs about 6 ms more (8 ms before, 14 ms after, the mean of 20 runs): the two misdrawn_characters calls.

Tests

File Test What it pins
tests/test_openreposhape_command.py test_a_repository_or_ref_no_line_can_print_is_refused_before_any_request (72) --version, openRepoShape Atlas --org acme (no $OPENREPOSHAPE_SETUP_SH, so it fetches) and --install from stdin; each variable holding an escape (C0), U+009B (C1), U+202E or U+200B before a forged tail, the repository as a URL with a leading -, and (fix round 1) a path, a URL and an scp address ending in a newline; under C and C.UTF-8: exit 2, the whole stderr is that one refusal in its words, the fake gh and curl log no call, nothing placed, stdout empty, no part of the value (no forged, no ESC, DEL, U+009B or character of MISDRAWN) in either stream
tests/test_openreposhape_command.py test_help_answers_past_a_repository_and_ref_no_line_can_print --help with both values bad: exit 0, the usage, neither value
tests/test_openreposhape_command.py test_bad_values_are_refused_in_the_order_the_check_states (2, fix round 1) a broken record and a bad repository: the record's refusal; a bad repository and a bad ref: the repository's; neither value echoed
tests/test_openreposhape_command.py test_version_prints_every_value_the_check_passes_as_typed (26) neither set prints openRepoShape (opensoft/openRepoShape @ main); each of the 7 CLONE_URLS forms (owner/name, URL, scp, POSIX path, three drive spellings); -acme/r, an owner/name whose clone URL starts with https (fix round 1); refs bad..ref, x.lock, -x and 256 characters; under C and C.UTF-8: exit 0, stdout byte for byte
tests/test_openreposhape_command.py test_version_prints_a_path_a_utf_8_locale_prints (7) a path holding each of NOT_CONTROLS (an e acute, U+00A0, U+200C, U+200D, U+2026, U+FF21, a subdivision flag) passes under C.UTF-8
tests/test_openreposhape_command.py test_a_ref_set_without_the_opt_in_is_never_recorded (docstring only) still: --install with bad..ref and no opt-in exits 0; the docstring now says "no check of the shape"
tests/test_install_sh.py test_openreposhape_repo_names_where_everything_comes_from[a-fork, every-character] install.sh's shown= parse still reads @ v9.9.9) from the real launcher's --version for an owner/name holding every character install.sh's checked_repo allows (ac.me-1_x/.open-Repo_Shape.2): exit 0, pinned, recorded, no "does not stay on" line

Runs (Linux, bash 5.2.21, Python 3.12.3; env -u GH_TOKEN -u GITHUB_TOKEN GH_CONFIG_DIR=<empty> /usr/bin/python3 -m pytest -q -p no:cacheprovider, one basetemp; the host's load average was about 68, hence the times):

Set 5cbec222 d183e769 a2af74a0 (fix round 1)
test_openreposhape_command.py, test_install_sh.py, test_openreposhape_package.py, test_install_sh_filter_repo.py, test_repo_hygiene.py, test_cli_reference.py, test_agents_md_codes.py 697 passed, 152.64s 784 passed, 509.81s (87 new) 806 passed, 255.96s (22 more)
test_setup_project_py.py, test_setup_sh.py, test_shape_doctor.py (the rest of what git grep -l -E 'OPENREPOSHAPE_(REPO|REF)|openRepoShape --version' -- tests names) 759 passed, 3 skipped, 1101.41s 759 passed, 3 skipped, 1185.61s 759 passed, 3 skipped, 1033.89s
the two launcher files in #867's predicted merge (0d66e9e8, with 2d1ff8a1) 645 passed, 121.55s

render-cli-reference.py --check: ok (no --help text changed).

Mutants, at a2af74a0. Each is one edit to the launcher in a copy of the head (a git archive, made a scratch repository so the doctor's clone has one), run against the selected cases (-k over both files, 130 cases) after an unmutated control (130 passed). The launcher at 5cbec222 in the same copy fails 73: the 72 refusal cases and the order row that needs a refusal at all.

Mutant Failed Killed by
M01 refuse_bad_values without checked_repo 51 the 48 repository cases; #841's a_repository_to_clone (2); the order row with a bad ref
M02 refuse_bad_values without the ref refusal 24 the 24 ref cases
M03 --version calls refuse_bad_record, as before 25 the 24 --version cases; the order row with a bad ref
M04 the top-level call is refuse_bad_record, as before 50 the 48 scaffold and install cases; a_repository_to_clone (2)
M05 checked_repo without the misdrawn check 15 the repository U+202E and U+200B cases (12); a_repository_to_clone (2); the order row with a bad ref
M06 checked_repo without *[![:print:]]* 30 the repository escape, U+009B and trailing-newline cases
M07 checked_repo without -* 6 the leading-dash cases
M08 ref_character_fault without the misdrawn check 16 the ref U+202E and U+200B cases (12); #841's a_ref_to_record, a_recorded_ref (4)
M09 ref_character_fault without the control check 13 the ref escape and U+009B cases (12); a_record_that_is_not_a_ref_is_refused_by_name[two-lines]
M10 ref_character_fault without its return 0 (the control words follow the misdrawn ones) 6 the ref U+202E and U+200B cases under C (the whole-stderr assertion)
M11 ref_fault without its return 0 after a character fault (the shape refusal follows) 3 a_recorded_ref (2), two-lines
M12 ref_fault without its character checks 3 a_recorded_ref (2), two-lines
M13 refuse_bad_values before the argument loop (--help no longer first) 6 test_help_answers_past_…; a_record_that_is_not_a_ref_is_refused_by_name (5, its --help assertion)
F01 fix round 1's case "$url$REPO" reverted to case "$url" 18 the 18 trailing-newline cases
F02 fix round 1's misdrawn_characters "$REPO" reverted to "$url" 0 survives, equivalent: the URL is $REPO or $REPO between ASCII, and $(…) drops only newlines, none of which is in MISDRAWN, so the two name the same characters
R03 (rv866) the repository checked before the record 1 the order row with a broken record
R04 (rv866) the ref checked before the repository 1 the order row with a bad ref
R07 (rv866) checked_repo's url="$REPO" 2 -acme/r printed by --version, both locales

M11 and M12 are killed only through the record: an $OPENREPOSHAPE_REF now meets the up-front check before checked_ref, so the record is the one way into ref_fault's character branch. rv866's R06 (the check reads ${OPENREPOSHAPE_REF:-main} rather than $REF) is equivalent and was not run again: a ref from the record has passed all of ref_fault, and a broken record leaves $REF at main. The runner, its logs and both kill tables (d183e769's and a2af74a0's) are in this PR's attachments, under mutants/; the probes under probes/.

Not changed

Closes #866

🤖 Generated with Claude Code

Summary by Sourcery

Validate repository and ref environment values before any output or operation, refusing terminal-unsafe and clone-invalid input without altering accepted values.

New Features:

  • Reject unsafe repository and ref values before they are printed, fetched, cloned, or otherwise used.

Bug Fixes:

  • Prevent control characters, terminal-misdrawing characters, leading-dash clone URLs, and trailing-newline repository values from bypassing validation or reaching requests and output.
  • Ensure validation occurs consistently for --version and normal command execution while preserving accepted values byte-for-byte.

Enhancements:

  • Consolidate record, repository, and ref validation into a single ordered preflight check while keeping --help available for invalid values.

Tests:

  • Add comprehensive command tests covering unsafe values, validation order, no-request guarantees, accepted repository and ref forms, locale behavior, and unchanged --version output.
  • Extend installer coverage to repository names containing all characters accepted by installer validation.

…cked once, before `--version` answers and before any fetch, so a control character or a character a terminal draws as another in either is refused, never printed or sent (#866)

`--version` printed both values as typed and exited 0. A scaffold run sent
its fetch with them, and so did `--install` from stdin, and each named
them three times in its refusal. So an escape, a C1 control, U+202E or
U+200B in either value reached the terminal as it was typed.

One function, `refuse_bad_values`, now runs once the arguments are read,
and inside the `--version` case, which answers within the argument loop. It
refuses the broken record first, as before. Then it checks
`$OPENREPOSHAPE_REPO` with `checked_repo`, `shape_checkout`'s guard moved
whole: #841's misdrawn characters, then a leading `-` or `[![:print:]]` on
the clone URL. Then it checks `$OPENREPOSHAPE_REF` with
`ref_character_fault`, `ref_fault`'s misdrawn and control-character checks,
moved whole. `ref_fault` calls them in the same place. Every refusal keeps
the words it had.

The ref's length and shape stay where a ref is used: `checked_ref` when
`--install` records, `shape_checkout` before `git checkout`, and setup.sh's
`--shape-ref` checks. So `--version` still prints any printable ref in
force, and an install that records no ref still takes one that names
nothing, as workBenches sets it.

`--version` refuses with exit 2, as a broken record already makes it do,
and does not respell the value. install.sh's `shown=` parse is unchanged
for every value it can hand the command: an owner/name that its own
`checked_repo` passed, and the tag that its `checked_version` passed. Both
pass this check, and `--version` prints them byte for byte.

`--help` still answers first. It names the variables, never their values.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 18, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR centralizes early safety validation for OPENREPOSHAPE_REPO and OPENREPOSHAPE_REF, rejecting terminal-control or confusable characters before output or network activity while preserving --help, accepted value forms, existing refusal wording, and installer compatibility.

File-Level Changes

Change Details Files
Adds a single pre-execution validation path for repository and ref environment values, while preserving help behavior and allowing values that pass the existing character rules to be displayed byte-for-byte.
  • Replaces both broken-record-only checks with shared repository/ref validation, including the --version argument path and the top-level execution path.
  • Moves repository clone-safety checks into checked_repo and ref character checks into ref_character_fault, preserving existing refusal messages and validation ordering.
  • Ensures invalid values fail with exit 2 before any version output, network request, clone, install, export, or other value-dependent operation; --help remains exempt.
  • Keeps shape/length validation scoped to places that use or record refs, so values such as bad..ref retain existing behavior when not opted into recording.
  • Retains -- on git clone and existing accepted repository forms, including URLs, scp addresses, local paths, and install.sh-compatible owner/name values.
openRepoShape
Expands command coverage to verify early rejection, output safety, accepted-value compatibility, locale behavior, and preservation of existing ref-recording semantics.
  • Adds parameterized tests for control characters, C1 controls, terminal-confusable characters, and leading-dash repositories across --version, scaffold, and stdin install flows.
  • Asserts invalid values produce only the expected refusal, make no fake GitHub requests, create no install artifacts, and leak no value bytes.
  • Verifies --help bypasses value checks and accepted repository/ref forms remain printed exactly as supplied, including non-ASCII paths under C.UTF-8.
  • Documents that non-opt-in refs remain unvalidated for shape while still receiving character-safety checks.
tests/test_openreposhape_command.py
Verifies installer compatibility for repository names containing every character allowed by install.sh's repository validation.
  • Parameterizes the installer repository test with a normal fork and an owner/name exercising all allowed characters.
  • Confirms the installed command's version parsing still succeeds and does not report a ref/version mismatch.
tests/test_install_sh.py

Assessment against linked issues

Issue Objective Addressed Explanation
#866 Check both repository and ref values for control characters and terminal-misdrawing characters before they can be printed or used in a fetch. ✅
#866 Apply the checks before --version answers and before any fetch, while allowing --help to answer without exposing the values. ✅
#866 Refuse unsafe values with exit 2, while preserving byte-for-byte --version output for values that pass the checks. ✅

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-2, 2026-10-18T03:52:54Z) Independent review of #872 (#866) at d183e769, for lane openRepoShape-2: READY

Nothing blocks the landing. Of the two SHOULDs, the first is a two-line fix that a probe has tested. The second is a ruling that a follow-up issue can carry.

SHOULD-1. A trailing newline in $OPENREPOSHAPE_REPO, given as a URL, an scp address or a path, passes the check and is then printed and sent.

  • Where: openRepoShape:720-725 (checked_repo).
    • url="$(shape_clone_url)": the $(…) drops trailing newlines.
    • So the misdrawn check (:721) and the -* | *[![:print:]]* case (:724) look at a value that no longer has them.
    • The lines that print and send $REPO still have them.
  • Not affected:
    • an owner/name, because .git follows the newline in the URL;
    • the ref, because it is checked as an argument.
  • Repro (env -i, LC_ALL=C.UTF-8, a stub gh and curl that log and fail):
    • OPENREPOSHAPE_REPO=$'/srv/r\n' openRepoShape --version exits 0. Stdout is openRepoShape (/srv/r, then a line feed, then @ main).
    • OPENREPOSHAPE_REPO=$'https://example.com/r\n\n' openRepoShape Atlas --org acme sends 2 requests, each carrying both newlines. Its refusal prints them three times.
    • git@example.com:r followed by a newline behaves the same way.
    • The base 5cbec222 is the same: shape_checkout had this blind spot too.
  • Why it matters: the title and the squash message say a control character in either value is "refused, never printed or sent". This input is a C0 control that is printed and sent.
  • Why it is not a MUST: nothing the person typed can follow the newline, so it cannot forge text. It only breaks a line, in a run that fails anyway.
  • Fix (probed): use misdrawn_characters "$REPO" at :721 and case "$url$REPO" in at :724.
    • The -* pattern still reads the start of the URL, so -acme/r passes as it does today.
    • $REPO brings the newline into the check.
  • What the probe showed with the fix:
    • it refuses all four trailing-newline values;
    • it still refuses an escape, U+202E and -x://acme/r;
    • it still prints the 7 CLONE_URLS forms and -acme/r;
    • both launcher test files pass with it: 560 passed.
  • Test to add: pytest.param("OPENREPOSHAPE_REPO", "/srv/forged\n", REPO_NOT_FOR_A_CLONE, id="repo-a-trailing-newline") in UNPRINTABLE_VALUES. Mutant R07 (url="$REPO") survives the current tests for this reason.

SHOULD-2. Under C or POSIX, every run except --help now refuses a URL or path in $OPENREPOSHAPE_REPO that holds a character past ASCII, and says it "carries a control character".

NIT-1. The order refuse_bad_values states is not pinned.

  • Mutants R03 (the repository checked before the record) and R04 (the ref checked before the repository) survive.
  • In practice the order makes no difference:
    • each refusal is right on its own;
    • a broken record and $OPENREPOSHAPE_REF cannot both apply, because the record is read only when the variable is unset.
  • Optional: add one case with a broken record and a bad repository, to pin "the record first", as the comment at :894 says.

NIT-2. A bare run with a bad value now exits 2 with the refusal, not 1 with the usage.

Checked and true

  • The issue, line by line.
    • --version, a scaffold run and --install from stdin each refuse with exit 2 and make no request. That holds for both variables, under C and C.UTF-8, with ESC, U+009B, U+202E and U+200B.
    • The 54 new refusal cases fail with the 5cbec222 launcher and pass at the head.
    • The 33 control cases pass on both.
    • A clean pair still prints openRepoShape (opensoft/openRepoShape @ main).
  • Every line that prints or sends $REPO or $REF runs after the check. The lines are :217, :224, :235-237, :301-306, :398-402, :748, :758-759, :933, :1054, :1244 and :1263.
    • Each is either in --version after refuse_bad_values, or reached only from :1178, :1187 or :1256, all of which come after :1164.
    • record_ref prints $REF only after checked_ref (:633).
    • shape_checkout runs checked_ref before git checkout.
    • ref_fault echoes a ref only after its character checks.
  • The departure is sound: the up-front check runs only the character checks, not the length and shape checks.
    • test_a_ref_set_without_the_opt_in_is_never_recorded needs --install with bad..ref and no opt-in to exit 0.
    • Mutant R05 (the whole ref_fault up front) breaks that test and 8 --version cases.
    • A ref of any length or shape prints as it reads, and the length and shape are still checked wherever a ref is used.
  • --version refusing with exit 2 breaks no caller.
    • install.sh's shown= parse (install.sh:571) holds: an owner/name that passed install.sh's checked_repo, and a tag that passed checked_version, both pass this check. The every-character case passes.
    • The --version steps in release.yml and tests.yml run the package's command, not this launcher. test_release_workflow.py passes.
    • workBenches' sentinel pinned-by-workBenches-no-fetch and its 40-hex ref pass. Its --version checks unset both variables.
  • The words are unchanged.
    • The refusal strings are the same set at base and head.
    • checked_repo's words are shape_checkout's and setup.sh:208's.
    • ref_fault's order is still: empty, length, misdrawn, control, shape. W10 and W11 are killed.
  • --help still answers first with both values bad: exit 0, 69 lines.
  • render-cli-reference.py --check: ok.
  • Sonar: no composite assert, no new function near cognitive complexity 15, and no repeated 4-line blocks.
  • Tests:
  • Merges:
  • Mutants: the writer's seven, re-run, fail as the writer reported. Of eight of my own, five are killed. R03 and R04 survive (NIT-1). R06 survives as an equivalent, and R07 survives as an equivalent apart from SHOULD-1's case.
  • The writer's candidate is confirmed. The package's openRepoShape --version (src/openreposhape/cli.py:385) prints U+202E and U+200B in $OPENREPOSHAPE_REPO as typed, with exit 0, under C.UTF-8 and C. It refuses an escape with exit 2. Not filed.

Could not verify

Lane: openreposhape-2 (openRepoShape-2)

…O` that ends in a newline is refused too, and the order of the up-front refusals is pinned (#866)

Fix round 1 on #872, after rv866's review.

SHOULD-1: `checked_repo` looked at `url="$(shape_clone_url)"`, and `$(…)`
drops the newlines at the end of what it captures. So a URL, an scp
address or a path ending in a newline passed the check. `--version` then
printed it, and a scaffold run sent it in both requests. An owner/name
keeps the newline inside its URL, before `.git`, so it was refused
already. The misdrawn characters are now looked for in `$REPO` as typed,
and the case reads `"$url$REPO"`: the leading `-` is still looked for in
the URL's first character, so `-acme/r`, which becomes
`https://github.com/-acme/r.git`, still passes, and a control character
anywhere in either is refused. The words are unchanged.

NIT-1: a new test holds the order `refuse_bad_values` states. With a broken
record and a bad repository, the record is refused. With a bad repository
and a bad ref, the repository is refused.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-2, 2026-10-18T04:44:45Z) Re-verification of #872 (#866)'s fix round 1 at a2af74a0: READY

Fix round 1 closes SHOULD-1 and NIT-1 with code and tests. SHOULD-2 and NIT-2 are now stated in the PR body, and those statements are true. No MUST and no SHOULD remain. One NIT, about the squash message's wording, is optional.

NIT-1. Two phrases in the squash message are slightly narrower than the code. File: lane 2's impl866-launcher-repo-ref/squash-message.md.

  • The third paragraph says a value ending in a newline "was sent by a scaffold run".
    • At d183e769, --install from stdin sent it too: 2 requests, each carrying the newline.
    • Suggested wording: "was sent by a scaffold run and by --install from stdin".
  • It also says the characters are looked for "in $REPO as typed as well as in the clone URL".
    • The misdrawn check now reads $REPO only (openRepoShape:729). Only the case at :732 reads both.
    • The result is the same, as F02 below shows. Optional rewording: "the misdrawn characters in $REPO as typed, and the control characters in it and in the clone URL".
  • Applied: lane 2 took both wordings into the squash message.

Checked and true

  • The delta d183e769..a2af74a0 is the two-line fix that rv866 probed, plus a comment. It changes openRepoShape:729 (misdrawn_characters "$REPO") and :732 (case "$url$REPO" in).
    • With comments stripped, it is byte for byte the reviewer's fix2 probe copy.
    • The refusal's words are unchanged.
  • Every trailing-newline form is now refused, with no request. Probe: env -i, a stub gh and curl, under both C and C.UTF-8.
    • These are refused at a2af74a0 but passed at d183e769: a path, a URL ending in two newlines, an scp address, ./rel/r, C:/src/r and C:\src\r, each with a newline at the end.
    • A scaffold run and --install from stdin with any of them now make 0 requests. At d183e769 they made 2 requests.
    • These are still refused: an owner/name with a trailing newline, a trailing CR, a newline before more text, ESC, U+009B, U+202E, U+200B, -x://acme/r and -x@h:r.
  • The forms that passed before still pass, and print the same bytes.
    • The 7 CLONE_URLS forms, -acme/r and /srv/café/x.git (under C.UTF-8) all exit 0.
    • Their stdout is byte for byte what d183e769 printed.
    • url can never be empty, so -* always tests the URL's first character.
  • F02 is equivalent. F02 is the misdrawn check reading the URL instead of $REPO.
    • The URL is either $REPO, or $REPO between ASCII bytes (https://github.com/ and .git). No ASCII byte can start or continue a UTF-8 sequence.
    • $(…) drops only newlines, and no newline is in MISDRAWN_CHARACTERS.
    • So both readings find the same characters, in the same order. The mutant also survives all 130 selected cases.
  • The new tests fail where they should.
    • The 18 trailing-newline cases fail with the d183e769 launcher and pass at a2af74a0.
    • The two order rows and -acme/r (2 cases) pass on both. They pin behaviour that already held: the order of the checks, and that -acme/r passes.
  • Mutants:
    • R03 is now killed by the row "a broken record and a bad repository".
    • R04 is killed by the row "a bad repository and a bad ref".
    • R07 is killed by -acme/r, in both locales.
    • F01 (reverting to case "$url") is killed by the 18 trailing-newline cases.
    • F03 (case "$REPO$url") is killed by -acme/r.
  • The PR body is accurate.
    • The "Not changed" table of the four runs under C matches the first review's probes.
    • The NIT-2 entry is accurate: five docs say "exits 1", and a broken record was already an exception none of them names.
    • The other "Not changed" lines still hold.
    • Decision 5's new line numbers are right: :1062, :1172, :1186, :1195, :1252, :1264, :1271 and :892.
    • The test counts are right: 72, 26 and 2.
  • The squash message's new paragraph is true, apart from NIT-1's two phrases. The title's claim, that a control character in either value is never printed or sent, now holds for a trailing newline.
  • Merges, over main d6071c7d and install.sh and openRepoShape: a $OPENREPOSHAPE_BIN_DIR the bytes refuse is read again in the locale's own charset, so a GB18030, GBK or Shift_JIS name that holds no control character and nothing drawn as another is placed, and where nothing can read it the bytes decide as before #867 at 8bb85cbf:
  • Test runs at a2af74a0:
    • the 8-file verify set: 862 passed, 22 more than at d183e769;
    • test_setup_project_py.py, test_setup_sh.py and test_shape_doctor.py: 759 passed, 3 skipped.
  • Scope note: setup.sh, install.sh and the openRepoShape command: each keeps its own copy of the old ref rule (*..* | *.lock), so a ref git refuses part by part (a.lock/b, a/.b, a//b) clones the standard before it fails #874 (filed by lane 5, now claimed by lane 2) will later change ref_fault's shape case. That case is now the only one left in ref_fault's else branch, next to the ref_character_fault call. It is out of this PR's scope and merges as a later change.

Could not verify

  • A GBK or GB18030 locale. None is installed here, as in the first review.
  • bash 3.2, Git Bash, and a real terminal emulator.

Lane: openreposhape-2 (openRepoShape-2)

@sonarqubecloud

Copy link
Copy Markdown

@brettheap
brettheap marked this pull request as ready for review October 18, 2026 06:07
@sourcery-ai

sourcery-ai Bot commented Oct 18, 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 days and 8 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@brettheap
brettheap merged commit c5d0bde into main Oct 18, 2026
5 checks passed
@brettheap
brettheap deleted the fix/866-launcher-repo-ref branch October 18, 2026 06:07
brettheap pushed a commit that referenced this pull request Oct 18, 2026
…nded as c5d0bde) as the stacked base of #874

Lane: openreposhape-2 (openRepoShape-2)
brettheap pushed a commit that referenced this pull request Oct 18, 2026
… its tree 4156a29 is #872's final head a1cad96's, merged above

Lane: openreposhape-2 (openRepoShape-2)
brettheap pushed a commit that referenced this pull request Oct 18, 2026
…nder the `C` locale of a URL, path or ref with a letter past ASCII as a control character, a regression from v1.1.0 through #872 (#866) that a UTF-8 locale gets past (#883 follow-up)

At lane 3 ask on #473: #893 is the v1.1.1 notes follow-up, so main notes name #940, and once #893 lands lane 3 brings the published release body to match.

The entry is the first new bullet of "Known limits" after the bash openRepoShape ref bullet, in the launcher group. Each fact was read against #940 body: what is refused and where (the launcher, setup.sh two guards, and install.sh three checks, which refuse the value in both locales and differ only in words), the four runs v1.1.0 ran, who meets it, the way around it, and the milestone.

Left out: lane 3 ruling of no v1.1.x back-port unless Brett Heap asks for a v1.1.2, which the body of #940 does not hold.

No other file changes.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 18, 2026
… the release commit, #778's leg-clone sentence and #802's readers sentence, each checked against runs at v1.1.0 and main (#883 follow-up) (#893)

The v1.1.1 notes landed as the release commit `5017830` (#883), and RV883's round 5 held two SHOULDs and five NITs for after it (#883 issuecomment-6250558821). This applies them to `docs/release-notes/v1.1.1.md`, with two more sentences lane 1's review found:

1. **#778's leg-clone sentence** says what runs at `v1.1.0` and `5017830` show. A leg clone whose own `AGENTS.md` says so keeps its leg row and its Triad row over each fault, where v1.1.0 gave `FINDING the way in refused` over a tab line and a traceback, exit 1, over a pattern that does not compile or a policy that raised on its name. A leg known only by its name gets a repository's two `n/a` rows.
2. **"Is", not "stays", exit 2,** in the pattern case, which was exit 1 in v1.1.0.
3. **#802's clause is dropped:** v1.1.0's `_blocks_inside` already read a `try`'s handlers, `else` and `finally` outside its guard.
4. **"What 1.1.1 fixes"** names "or cannot be used", and `the other way in`.
5. **#815's leftover tracebacks** were measured on Python 3.12; 3.13 is read, not run.
6. **#871 lets `_` through for any owner,** an organisation's name included.
7. **#869 asks the disk** only about a name that is not UTF-8.
8. **#802's readers:** at v1.1.0 only the import reader skipped an `if TYPE_CHECKING:` body; the binding reader skips one now.
9. **A known limit names #940:** under the `C` or POSIX locale, the bash `openRepoShape` and `setup.sh`'s guards refuse a URL, path or ref with a letter past ASCII as "a control character", a regression from v1.1.0 in the launcher through #872 (#866); a UTF-8 locale takes such a URL or path.

The draft-time lines in the sections marked "remove at tag time" say main `2d372bb`, where #778 landed file for file as its reviewed head `8259ea1`, and that #879 moved to v1.2.0. The release body never carries those sections.

Review: lane 1's independent reviewer found `b795ef74` NOT READY, on the leg-clone sentence, and `ca49f125` READY, with each changed sentence checked against runs at `v1.1.0` and `5017830`. Lane 1's second reviewer found `6cdb5551`, which adds the #940 limit, READY, with each clause measured against the launcher at `64464d31` and at v1.1.0.

It lands by fast refresh (Brett Heap, 2026-10-15T22:48:35Z). Its reviewed head `6cdb5551` is green on Linux and SonarCloud. The branch merged main once and was never rebased; the last merge is update-branch's, clean, giving f60654e, which is green on SonarCloud and Git Bash. Lane 1's gate passed on its tree: the 5 test files that read the release notes or the workflows (`test_repo_hygiene.py`, `test_release_workflow.py`, `test_workflow_concurrency.py`, `test_agents_md_codes.py` and `test_cli_reference.py`), 221 passed and 0 skipped.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 18, 2026
…ter past ASCII is no control character under `C` either, as #760's byte rule decides in all seven checks that used `[![:print:]]`, and every control character is still refused (#940)

#872 checked `$OPENREPOSHAPE_REPO` and `$OPENREPOSHAPE_REF` before every
run but `--help`, with `[![:print:]]`, which under `C` and POSIX matches
every byte past ASCII: a mirror at `/srv/café/openRepoShape.git`, or a ref
`café`, was refused as "carrying a control character" by `--version`, a
scaffold run, `--install` and `--preflight`, where `C.UTF-8` took it.
`setup.sh`'s two guards and `install.sh`'s `checked_repo`,
`checked_version` and option check held the same class.

All seven now ask #760's rule, as `install_commands` holds the directory to
it: under `LC_ALL=C`, in a subshell, `[[:cntrl:]]` or U+0080 to U+009F's
two bytes, so one value gets one answer in every locale, and the launcher,
`setup.sh`, `install.sh` and the package agree. The leading `-` arms stay,
`misdrawn_characters` stays first, and no refusal's words change. A lone
`0x9B` byte that is not UTF-8 now passes in both locales, the trade #867
took for the directory, which the comment at each place states. `setup.sh`,
which has no copy of `misdrawn_characters`, also refuses U+2028 and U+2029
by their bytes, as it refused both in every locale before. The redirect's
location in `install.sh` keeps `[![:print:]]`: it is the network's, and
that class prints no lone C1 byte from it.

One table, `CONTROL_RULE` in `tests/conftest.py`, holds the seven to one
answer under `C` and `C.UTF-8`: `café`, a lone `0x9B` and a lone `0xE9`
taken; an escape, U+009B, DEL, a tab, U+0085, U+0080, U+009F, a newline at
the end and a lone `0xE9` then U+009B refused; U+202E, U+200B, U+2028 and
U+2029 refused by code point. The three test files read it, and a
repository or ref with `café` goes through every launcher run in both
locales. Every arm of the rule dropped from any place fails a test, the
seven the issue found surviving included.

Closes #940

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 20, 2026
…ter past ASCII is no control character under `C` either, as #760's byte rule decides in all seven checks that used `[![:print:]]`, and every control character is still refused (#940) (#986)

#872 checked `$OPENREPOSHAPE_REPO` and `$OPENREPOSHAPE_REF` before every
run but `--help`, with `[![:print:]]`, which under `C` and POSIX matches
every byte past ASCII: a mirror at `/srv/café/openRepoShape.git`, or a ref
`café`, was refused as "carrying a control character" by `--version`, a
scaffold run, `--install` and `--preflight`, where `C.UTF-8` took it.
`setup.sh`'s two guards and `install.sh`'s `checked_repo`,
`checked_version` and option check held the same class.

All seven now ask #760's rule, as `install_commands` holds the directory to
it: under `LC_ALL=C`, in a subshell, `[[:cntrl:]]` or U+0080 to U+009F's
two bytes, so one value gets one answer in every locale, and the launcher,
`setup.sh`, `install.sh` and the package agree. The leading `-` arms stay,
`misdrawn_characters` stays first, and no refusal's words change. A byte
that is not UTF-8, as a lone `0x9B` or an overlong form of a control
(`0xC0 0x9B`), now passes in both locales, the trade #867 took for the
directory, which the comment at each place states. `setup.sh`, which has
no copy of `misdrawn_characters`, also refuses U+2028 and U+2029 by their
bytes, as it refused both in every locale before.

The network's values get no such trade. `install.sh` still names the
redirect's location only where the locale's `[![:print:]]` finds nothing
in it, and it now takes the tag in a `…/releases/tag/<tag>` location only
when that tag is printable ASCII under `LC_ALL=C`; any other tag names no
release, in every locale, and `gh` is asked next. Without that, a tag
holding a lone `0x9B` would pass `checked_version` by the trade, and its
shape refusal would print the byte as it came from the network. A release
tag is ASCII, so this refuses no real one.

One table, `CONTROL_RULE` in `tests/conftest.py`, holds the seven to one
answer under `C` and `C.UTF-8`: `café`, a lone `0x9B`, a lone `0xE9` and an
overlong escape taken; an escape, U+009B, DEL, a tab, U+0085, U+0080,
U+009F, a newline at the end and a lone `0xE9` then U+009B refused; U+202E,
U+200B, U+2028 and U+2029 refused by code point. The three test files read
it, and a repository or ref with `café` goes through every launcher run in
both locales. A test holds the seven lines to one text, so a check without
`LC_ALL=C`, which no locale installed here answers differently, fails it,
and the redirect's test gives a tag with a lone `0x9B` or a letter past
ASCII no answer but "names no release". Every arm of the rule dropped from
any place fails a test, the seven the issue found surviving included.

Independent review READY at H0 d4bba68 (#986 6277825631), with H0's tests and SonarCloud green. Refreshed by update-branch onto main af0e60f as 2a6554c, a merge whose tree 3e049cd lane 2's gate ran on (8 files: 165 passed; 961 passed; 612 passed), with SonarCloud green there. Landed on Brett Heap's word "merge v1.2.0 PRs once reviewed and green" (2026-10-18T14:45:28Z, #473 6251255596).

Closes #940

Lane: openreposhape-2 (openRepoShape-2)
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