Skip to content

setup-project.py: the self-bootstrap refuses an $OPENREPOSHAPE_REPO holding U+202E or another character the launcher refuses, by code point and in the launcher's words, before it prints the value or runs git clone (#953) - #1043

Merged
brettheap merged 1 commit into
mainfrom
fix/953-repo-misdrawn
Oct 20, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-5 (openRepoShape-5)
Claim: #953 (comment)

Closes #953.

Goal

setup-project.py's self-bootstrap refuses an $OPENREPOSHAPE_REPO holding a character the launcher refuses (U+202E and the rest of MISDRAWN_CHARACTERS). It refuses by code point, in the launcher's words, before it prints the value or hands it to git.

Head c42afa30, one commit on origin/main 5b41d44f. Only setup-project.py and a new tests/test_setup_project_repo_misdrawn.py change. No flag, no --help line and no doc changes.

What changed

setup-project.py (section 0, just above self_bootstrap):

  • MISDRAWN_CHARACTERS is the launcher's list (openRepoShape:502 at 5b41d44f), entry for entry, in the package's tuple form. It is a copy, for checked_value's reason: self-bootstrap mode runs before there is a checkout to read the launcher from.
  • misdrawn_characters(value) names the characters a value holds as U+XXXX, in code point order. It is the package's standard.misdrawn_characters, and it looks for characters, as CONTROL_RE does.
  • checked_repo(value) asks that first. It refuses with the launcher's checked_repo sentence, which names the code points and nothing else of the value. Then it calls checked_value(..., PATH_RE), unchanged, for a leading - and a control character.
  • self_bootstrap reads a non-empty $OPENREPOSHAPE_REPO through checked_repo. Unset or empty still means the default. The check stays where --org is asked, so the order is the same: --org first, then the repository, then openRepoShape setup is printed, then mkdtemp and git clone.
  • The value's raw bytes are never printed. The refusal goes through die, so it is REFUSED: ... on stderr with exit 2.

tests/test_setup_project_repo_misdrawn.py, 22 tests:

"No git call", stated exactly. The refused run makes one git call, git -C <dir> rev-parse --show-toplevel. That is is_shape_checkout, which runs before self_bootstrap on every run past --help and --preflight. It asks whether the file stands in a checkout, which is how the run knows it is self-bootstrapping at all, and it names nothing of the value. No git call names the value, and there is no clone. Moving the check above the probe would also refuse the value on the developer path, which never reads it. It would also leave setup-project.py ordered differently from setup.sh, which probes first and guards only on its self-bootstrap path.

Probes, before and after

These run $Q/v118/w953/probe.py. Each case uses a fresh scratch folder, a logging git stub that then runs the real git, a gh that fails, GH_TOKEN=bogus, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false and GIT_CONFIG_GLOBAL=/dev/null. "Before" is git archive 5b41d44f; "after" is the worktree. Results are the same under git 2.43.0 and 2.47.1.

Case Before (5b41d44f) After
package, ZIP tree, Atlas --org acme, acme/r[U+202E]forged exit 128; E2 80 AE on stdout and stderr; git: probe, then clone --quiet -- acme/r[U+202E]forged /tmp/openreposhape-shape-... exit 2; no E2 80 AE; git: probe only; stdout is the package's one line
direct, Atlas --org acme, same value exit 128; raw bytes in the clone line; probe + clone exit 2; refusal names U+202E; probe only
direct, --org acme, same value exit 128; probe + clone exit 2; probe only
direct, acme/r[U+200B]forged exit 128; probe + clone exit 2; names U+200B
direct, acme/r[U+E0001]forged exit 128; probe + clone exit 2; names U+E0001
direct, acme/r[U+2026]b (not listed) exit 128; probe + clone the same: probe + clone, exit 128

The "after" refusal, from the probe:

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.

Tests that fail at main

The new file was run on main's setup-project.py (git show 5b41d44f:setup-project.py, put back byte for byte afterwards): 15 failed, 7 passed.

  • Failed: both parity tests (AttributeError: no MISDRAWN_CHARACTERS, no misdrawn_characters), all 12 direct tests, and the package test.
  • Passed: the 7 NOT_CONTROLS tests. They pass at main by design, because they guard against a check that is too broad.

Mutants

Each mutant was applied alone ($Q/v118/w953/mutate.py), the new file was run (22 tests), and both files were put back. All 18 are killed.

Mutant Failed
m01 check removed (main's read) 13
m02a check moved after the (0) self-bootstrap line 13
m02b check moved after the clone line 13
m03a only the value's first character checked 15
m03b only the table's first entry checked 13
m04 only U+202E checked 8 (U+00AD, U+200B, U+E0001, two-at-once)
m05 refusal exits 1 13
m06a table drops E0001 4
m06b parity compares the first five entries + table drops E0001 4 (parity's second assert, SHARING_ITS_BYTES, both U+E0001 tests)
m06c parity compares the first five + table adds 200C 2 (SHARING_ITS_BYTES, the Persian/U+200C test)
m06d parity compares the first five + 2060-206F narrowed to 206E 2
m06e parity uses set inclusion + table drops E0001 4
m07 a range read without its last code point 15
m08 control check asked before the code points 2 (both tab-then-U+202E tests)
m09 characters named in the value's order 3
m10 every character past ASCII refused 7 (every NOT_CONTROLS test)
m11 refusal quotes the value back 13
m12 check skipped on a hand-over (OPENREPOSHAPE_SELF_BOOTSTRAP=1) 1 (the package test alone)

Test runs

All at c42afa30, one pytest process at a time: /usr/bin/python3 -m pytest -q -p no:cacheprovider -o tmp_path_retention_policy=failed, one reused basetemp, EMAIL=service@opensoft.dev, a stub gh first on PATH, GH_TOKEN=bogus, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, GIT_CONFIG_GLOBAL=/dev/null.

Run git Files Result
h1 2.43.0 test_setup_project_repo_misdrawn.py (22), test_setup_project_ref_parts.py (27), test_openreposhape_package.py (213), test_readme_windows_quoting_sentence.py (44), test_release_install_check_children.py (30), test_repo_hygiene.py (100), test_agents_md_codes.py (47), test_cli_reference.py (23) 506 passed in 94.06s
h2 2.43.0 test_setup_project_py.py 340 passed in 122.68s
h3 2.43.0 test_setup_sh.py 191 passed in 96.62s
h4 2.47.1 test_setup_project_repo_misdrawn.py, test_setup_project_ref_parts.py, test_setup_project_py.py 389 passed in 128.13s
h5 2.43.0 test_no_auto_maintenance.py, test_setup_project_dry_run_offers.py, test_validator_child_encoding.py, test_setup_project_name_edges.py, test_setup_project_typed_name.py 353 passed, 2 skipped in 73.92s (the two skips: GIT_CONFIG_GLOBAL was set)

No shell_quoted call was added (count 0), so the README's and the handbook's Windows-quoting sentences and NAMED are unchanged. setup-project.py is not a pinned copy. The wheel takes it at build time (pyproject.toml force-include), and the tests that read its source (test_setup_project_py.py, test_release_install_check_children.py, test_repo_hygiene.py) pass.

Merges

Decided

  • RV947's companion is left out. That companion is the package's scaffold run asking the repository even beside a checkout.
  • Found while measuring, not changed here: CONTROL_RE and PATH_RE hold \x00-\x1f\x7f only, so a C1 control passes checked_value. The launcher and the package refuse C1 (U+0080 to U+009F) in a repository. With acme/r[U+009B]31mforged, the clone line prints the raw C2 9B, before and after this change. --into and --local-remote-dir go through the same PATH_RE (read in the code, not measured). That would be a follow-up for checked_value, which every flag shares.

🤖 Generated with Claude Code

Summary by Sourcery

Harden setup-project self-bootstrap by refusing visually misleading repository values before output or cloning.

New Features:

  • Reject repository environment values containing launcher-defined visually misleading characters before they are printed or passed to git.

Bug Fixes:

  • Prevent unsafe repository names from exposing misleading Unicode characters during self-bootstrap or reaching git clone.

Enhancements:

  • Keep the repository-character validation aligned with the launcher's code-point list and refusal message while preserving existing valid Unicode repository behavior.

Tests:

  • Add coverage for launcher parity, code-point reporting, refusal ordering, output redaction, git-call behavior, valid Unicode values, and package handoff scenarios.

… holding U+202E, U+200B or another character the launcher refuses, by code point and in the launcher's words, before it prints the value or hands it to `git clone`, as a downloaded copy and the package's `openRepoShape` beside a tree with no `.git` both reach it (#953)

#953: the self-bootstrap held `$OPENREPOSHAPE_REPO` to
`checked_value(..., PATH_RE)`, which refuses a leading `-` and a control
character and takes every character of the launcher's
`MISDRAWN_CHARACTERS`. So `acme/r`, U+202E, `forged` was printed raw in
the clone line (`E2 80 AE`) and handed to `git clone`, which exited 128.
A downloaded `setup-project.py` reaches it (`python3 setup-project.py
--org acme`), and so does the package's `openRepoShape` beside a source
tree with no `.git` (a ZIP, or an sdist installed with
`pip install -e .`): `beside_checkout` looks for files only, so the
package hands over to that tree's `setup-project.py`, which finds no
checkout and self-bootstraps.

`setup-project.py` now carries the launcher's list as
`MISDRAWN_CHARACTERS`, entry for entry, and `misdrawn_characters`, as the
package does. `checked_repo` asks it first and refuses in the launcher's
words, naming each character by its code point; `checked_value` then asks
what it asked before. Both run before the first line the self-bootstrap
prints and before the clone, so the run exits 2 and prints nothing of the
value. The one git call before them is `is_shape_checkout`'s, which asks
whether the file stands in a checkout and names nothing of the value.

tests/test_setup_project_repo_misdrawn.py: the copy held to the
launcher's list and to `SHARING_ITS_BYTES`; the issue's run and
`--org acme` alone refused, for U+202E, U+00AD, U+200B, U+E0001, two at
once and a tab before U+202E, under a logging git stub that sees only the
checkout probe; the launcher's `NOT_CONTROLS` cloned as before; and the
package's `openRepoShape` beside a tree with no `.git` refused the same
way.

The package's own scaffold run still does not ask the repository beside
a checkout (RV947's companion): beside a git checkout no run reads the
value, and beside a tree with no `.git` this check refuses it.

Closes #953

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

sourcery-ai Bot commented Oct 20, 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 1 day and 13 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 20, 2026

Copy link
Copy Markdown

Reviewer's Guide

Self-bootstrap now rejects OPENREPOSHAPE_REPO values containing launcher-defined misdrawn characters by code point, using launcher-compatible diagnostics before printing or cloning, while retaining existing validation and allowing other Unicode values. New tests verify launcher parity, sanitized output, git-call behavior, direct and package bootstrap paths, and regression compatibility.

Sequence diagram for safe repository self-bootstrap validation

sequenceDiagram
    participant Launcher as setup-project.py
    participant Git as git
    participant Output as stdout/stderr

    Launcher->>Git: is_shape_checkout()
    Git-->>Launcher: checkout probe result
    Launcher->>Launcher: checked_repo(OPENREPOSHAPE_REPO)
    alt misdrawn character found
        Launcher->>Output: die(launcher's refusal with U+ code points)
        Output-->>Launcher: exit 2
    else value is valid
        Launcher->>Output: say(openRepoShape setup)
        Launcher->>Git: git clone --quiet -- repository target
    end
Loading

Flow diagram for OPENREPOSHAPE_REPO validation

flowchart TD
    A[Read OPENREPOSHAPE_REPO] --> B{Value empty or unset?}
    B -->|Yes| C[Use DEFAULT_SHAPE_REPO]
    B -->|No| D[checked_repo]
    D --> E[misdrawn_characters]
    E --> F{Misdrawn characters found?}
    F -->|Yes| G[die with U+ code points]
    G --> H[Exit 2 without printing value or cloning]
    F -->|No| I[checked_value with PATH_RE]
    I --> J{Existing validation passes?}
    J -->|No| K[Refuse value]
    J -->|Yes| L[Print setup line and run git clone]
Loading

File-Level Changes

Change Details Files
Added launcher-parity validation for unsafe repository characters before self-bootstrap output or cloning.
  • Copied the launcher's MISDRAWN_CHARACTERS table into setup-project.py for pre-checkout use.
  • Implemented code-point detection and launcher-matching refusal text without echoing the repository value.
  • Applied the new check to non-empty OPENREPOSHAPE_REPO while preserving defaults, ordering, and existing PATH_RE validation.
setup-project.py
Added comprehensive regression and parity coverage for repository-character handling across direct and package bootstrap paths.
  • Verified the character table and naming behavior against launcher data and byte-sharing cases.
  • Covered refusal, output sanitization, exit status, git-call ordering, multi-character values, and package handoff behavior.
  • Confirmed allowed non-misdrawn Unicode values still reach the existing clone path; skipped logging-stub cases on Windows.
tests/test_setup_project_repo_misdrawn.py

Assessment against linked issues

Issue Objective Addressed Explanation
#953 In setup-project.py self-bootstrap, check a non-empty $OPENREPOSHAPE_REPO against the launcher's misdrawn-character list by code point, and refuse with the launcher's wording. ✅
#953 Perform the refusal before printing the repository value or passing it to git clone, including when self-bootstrap is reached through the package beside a tree without .git. ✅
#953 Keep the self-bootstrap copy of the character list aligned with the launcher through a parity test. ✅

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-5, 2026-10-20T05:57Z) @codex review

@sonarqubecloud

Copy link
Copy Markdown

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T06:01:36.775733Z c42afa3 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: c42afa3086

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@brettheap

Copy link
Copy Markdown
Contributor Author

Independent review of #1043 (#953) at c42afa30, for lane openRepoShape-5: READY

No MUST. One SHOULD, and it is a follow-up issue that does not block this PR. One NIT about a gap in the tests. Head c42afa3086bc08f41c4444d19f3bd3f9e3f3cd23 is one commit on 5b41d44f. I re-fetched main and ran ls-remote: main is still 5b41d44f.

Findings

SHOULD-1 (file a follow-up issue; do not fix it here). A C1 control in $OPENREPOSHAPE_REPO is still printed raw and handed to git. setup-project.py:442 (PATH_RE) and :448 (CONTROL_RE) only cover \x00-\x1f\x7f.

  • The probe confirms the writer's report. With acme/r + U+009B + 31mforged and --org acme, the head exits 128. Stdout holds the raw bytes C2 9B in the clone line, and git is called as git clone --quiet -- acme/r\x9b31mforged .... Head and base give the same output byte for byte.
  • Every other way in refuses it:
    • the launcher, openRepoShape:829 (*$'\302'[$'\200'-$'\237']*);
    • setup.sh:233;
    • the package, src/openreposhape/standard.py:76 ([\x00-\x1f\x7f-\x9f]).
  • Why a follow-up and not this PR:
    • The issue's title, Direction and Verify limit it to the launcher's list. The Goal's "by code point" describes the list check, not the control check.
    • The right fix is CONTROL_RE/PATH_RE, which every flag shares (checked_path for --into and --local-remote-dir at :632-634; I read that code but did not measure it). Widening them changes those refusals too. refuse_value already quotes with ascii(), so the wider check is safe.
    • Patching checked_repo alone would give a third way of saying the same refusal.
    • The problem is old (same at base), and the body already reports it under "Found while measuring".

NIT-1. No refused value in the tests starts or ends with a listed character. In tests/test_setup_project_repo_misdrawn.py:68-78, every REFUSED value is acme/r + character + forged.

  • My mutants r6 (misdrawn_characters(value[1:])) and r7 (value[:-1]) both survive: 22 passed.
  • A plausible regression in that blind spot: someone adds .strip() before the check, and Python's str.strip() removes U+2028 and U+2029.
  • Fix: add two cases, such as RLO + "forged" and "acme/r" + chr(0x2028).

Rulings on the focus items

1. The issue's Verify says "makes no git call"; the refused run still makes the checkout probe. Keep it. No code change.

  • It never names the value. In all 13 refused runs on the head, the git log is only git -C <script dir> rev-parse --show-toplevel, with no byte at or above 0x80 in git's arguments. Its output is captured and never printed. Git does not read OPENREPOSHAPE_REPO.

  • It does not depend on the value. The probe runs at base and at head on every run past --help and --preflight, whatever the variable holds: the no---org and tab cases log the same single probe at base.

  • It does not act on the value. On the developer path (a real checkout), a run with the issue's value and a run with the variable unset gave the same exit code, output and git calls.

  • The Goal is met. It asks for the refusal "before it prints the value or hands it to git".

  • Moving the check above the probe would cost more than it gains. The probe is the only way to know the run is self-bootstrapping, so the check would also run on:

    • the developer path, which never uses the value;
    • setup.sh's hand-over (OPENREPOSHAPE_SELF_BOOTSTRAP=1). By then setup.sh, which refuses only C0, C1, U+2028 and U+2029, has already printed and cloned a U+202E value. The run would be refused after the damage, and a run that completes today would exit 2.
    • the package's hand-over beside a git checkout, which is RV947's companion question coming in by another door.

    It would also break the mirror with setup.sh, which probes at :114 and guards at :233. All it would remove is one call that names nothing.

  • For the coordinator: record the issue's Verify as met in substance ("no git call names the value, and there is no clone") in the landing or closing comment. The body's paragraph "No git call, stated exactly" already says this.

2. The list copy.

  • The table equals the launcher's, all 10 entries.
  • I ran the head's function against the launcher's own bash function under LC_ALL=C on 63 values: two below to two above both ends of every entry, all 41 at once, and two pairs. They disagreed on none. Ranges include both ends, as the launcher reads them.
  • I made 9 kinds of drift. Every one fails the parity test:
    • drop E0001;
    • add 200C;
    • narrow to 206E;
    • widen to 2070;
    • widen the start to 205F;
    • swap two entries;
    • split 200E-200F into two entries;
    • the launcher drops E0001;
    • the launcher narrows its range.

3. The refusal.

  • It exits 2, and the sentence matches openRepoShape:828 word for word once $misdrawn is filled in.
  • With all 41 characters at once, it names 41 code points in code point order.
  • No value bytes reach either stream.
  • Direct run: stdout is empty. Package beside a tree with no .git: stdout is only the package's one line.
  • There is no clone on either path. At base, the same runs exit 128 with E2 80 AE in stdout and in git's arguments.

4. What must still pass, and which message wins.

  • These 20 cases are byte-for-byte the same at head and base (exit code, stdout, stderr and git log, with temp paths normalised): every NOT_CONTROLS value, U+205F, U+2070, acme/repo, a URL, a URL with --shape-ref, /srv/café/..., ./rel/..., empty, unset, a C0 tab, --upload-pack=touch, the C1 value, no --org, --help and --preflight.
  • When a value has both kinds of fault, the code-point sentence wins, in the launcher's order. I checked tab + U+202E, newline + REFUSED: + U+202E, and -x + U+202E. A value with only a control character or a leading - still gets checked_value's unchanged sentence.

5. The C1 gap is real. See SHOULD-1: a follow-up issue.

6. The writer was right to leave out RV947's companion.

Checked and true

  • Body claims:

    • 22 tests.
    • At base: 15 failed, 7 passed, the same split as the body.
    • The writer's mutants m04, m07 and m08 kill 8, 15 and 2 tests, as the body says.
    • The merge tree is 59de4c261c22.
    • The list is at openRepoShape:502.
    • The diff adds no shell_quoted call.
  • Runs at the head, all through run9.sh:

    Run git Files Result
    A 2.43 new file, package, trio 405 passed (22+213+100+47+23)
    B 2.43 test_setup_project_py.py 340 passed
    C 2.47.1, at the exact path new file 22 passed
  • My mutants on the changed lines (tests failed):

    Mutant Failed
    range start + 1 10
    names joined with , 3
    U+%X 4
    one name per range 1
    sentence tail dropped 13
    clone header printed before the check 13
    checked_value dropped 0 in the new file; killed by test_a_hostile_openreposhape_repo_refuses
    r6 / r7 survive (NIT-1)
  • Merge with origin/main 5b41d44f: clean, tree 59de4c261c228274a5cff18aff953211de7002e3.

  • Sonar-style checks:

    • Cognitive complexity: misdrawn_characters 7, checked_repo 1, self_bootstrap 11 and unchanged.
    • No assert joins conditions with and.
    • New lines are at most 80 wide; the file's widest is 126.
    • Neither file has a non-ASCII byte.
  • Docs and docstrings are true. setup-project.py is not a pinned copy.

Could not verify

  • Windows, CI's git 2.55 and the real Sonar analysis. Duplication against standard.py:195-201 is about 6 lines, which I judge to be under the threshold without running Sonar.
  • What real git writes to stderr: my stub fails the clone silently, so the body's "before" stderr bytes are the writer's measurement, not mine.
  • Whether other open PRs touch these files. I did not run gh; the brief says none do.
  • C1 through --into and --local-remote-dir: I read the code but did not run it.

Checklist candidates

  • When a guard is copied into a second entry point, compare its control-character class with the original's.
  • Put a forbidden character at the start, the middle and the end of refused test values.
  • When a Verify says "no git call", rule explicitly on any probe that runs before the check and does not depend on the value.

Reviewer: rv953, lane 5's independent reviewer, not the writer (w953).

Lane: openreposhape-5 (openRepoShape-5)

@brettheap
brettheap merged commit dc7cd49 into main Oct 20, 2026
5 checks passed
@brettheap
brettheap deleted the fix/953-repo-misdrawn branch October 20, 2026 07:28
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