Skip to content

setup-project.py and the assembly root's bootstrap.py: every Python child is told the encoding its words are read in, so a cp1252 stdout gets one encoding from setup, and make bootstrap passes a project whose name the code page cannot hold - #851

Merged
brettheap merged 11 commits into
mainfrom
fix/836-child-environment
Oct 18, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

setup-project.py and the assembly root's bootstrap.py: every Python child is told the encoding its words are read in, so a cp1252 stdout gets one encoding from setup, and make bootstrap passes a project whose name the code page cannot hold

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

Stacked on

What changed

1. setup-project.py: every child of run is told what its words are read in. main reconfigures stdout and stderr to UTF-8, and a child does not inherit a reconfigure: a Python child takes its streams' encoding from its own environment. So under PYTHONIOENCODING=cp1252:strict or cp1252:surrogateescape (Windows' ANSI code page on a stdout that is not the console), five children wrote cp1252 into the UTF-8 stream, and the sixth, the plan run again to read why it was refused, wrote cp1252 into a pipe run decodes as UTF-8.

2. The assembly root's scripts/bootstrap.py: the validators are told what bootstrap's stdout is. _run passes env=inherited_child_environment(), a copy of the library's: a project does not carry shape_materialize.py, and a pinned script that imported a new name out of repo_shape.py would be refused at update-shape.py apply (update-accept-local-missing-name, #536, tests/test_update_shape_kept_helper.py), so the copy keeps repo_shape.py untouched, as the validators keep their own _shown. This is a shape bump: every project re-pins scripts/bootstrap.py. No digest is committed anywhere in the repository; a scaffold computes contracts/shape-pin.yaml from the bytes it copies.

3. tests/test_validator_child_encoding.py. The setup-project.py row leaves NOT_YET (which is now empty, its comment kept), LAUNCHERS gains templates/assembly-root/scripts/bootstrap.py: inherited_child_environment, and the tests below are new.

No message's wording changes. The bytes do, measured by a probe (setup-project.py --project Sample --yes --org demoorg --local-remote-dir <d> --into <d>, a gh stub that exits 97 first on PATH, GH_TOKEN=bogus, an empty GH_CONFIG_DIR):

setup-project.py, PYTHONIOENCODING=cp1252:strict (cp1252:surrogateescape: the same)
  before (db693114): exit 0, 129 stdout lines, 9 not UTF-8 (24 31 38 45 64 79 80 100 102)
    line 24: ...at the root of the repository \x97 `kind: family-manifest`...     (U+2014 in cp1252)
    line 45: shape        opensoft/openRepoShape @ db693114a950 (tree e67601b2f778\x85)
  after:            exit 0, 129 stdout lines, 0 not UTF-8
    line 24: ...at the root of the repository \xe2\x80\x94 `kind: family-manifest`...
    line 45: shape        opensoft/openRepoShape @ db693114a950 (tree e67601b2f778\xe2\x80\xa6)
  utf-8:strict, before and after: exit 0, 129 lines, 0 not UTF-8

scripts/bootstrap.py of a scaffolded project with `name: "日本"`, PYTHONIOENCODING=cp1252:strict
  before: bootstrap: 日本 (<root>)
          ...
            --- manifest ---
          Traceback (most recent call last):
            ...
          UnicodeEncodeError: 'charmap' codec can't encode characters in position 13-14: character maps to <undefined>
          bootstrap FAILED: manifest (exit 1)                                     exit 1
  after:  bootstrap: 日本 (<root>)
          ...
            --- manifest ---
          manifest ok: 日本 (sample), 3 legs
          ...
          bootstrap ok                                                            exit 0
  cp1252:surrogateescape: as cp1252:strict. cp1252:replace: `manifest ok: ?? (sample), 3 legs`,
  exit 0, before and after. utf-8:strict: `manifest ok: 日本 (sample), 3 legs`, exit 0, before and after.

A new bootstrap.py copied by hand into a project scaffolded before it is refused shape-copy-drift by that project's own pin, as it should be: the change reaches a project through update-shape.py apply.

Tests

file test what it pins
tests/test_validator_child_encoding.py test_each_copy_tells_an_inherited_child_what_the_library_does (2 copies x 14 INHERITED streams) each copy (COPIES: setup-project.py's child_environment, the bootstrap's inherited_child_environment) builds the very environment shape_materialize.inherited_child_environment builds, read from the stdout it runs under
test_each_copy_leaves_the_environment_alone_with_no_stdout (2 x 3) no stdout, no encoding, an encoding no codec answers to: PYTHONIOENCODING left as it was
test_each_copy_keeps_the_handlers_the_library_keeps (2) HANDLERS_THAT_NEVER_RAISE equals the library's _HANDLERS_THAT_NEVER_RAISE
test_setup_projects_copy_tells_a_captured_child_what_the_library_does child_environment(capture=True) equals captured_child_environment(), under an operator's cp1252:strict
test_setup_projects_run_tells_each_child_what_its_words_are_read_in (3) run itself: an inherited child is told utf-8:replace (the stdout main leaves) or cp1252:backslashreplace (one it could not reconfigure); a captured one utf-8:backslashreplace, and run reads manifest ok: 日本 back
test_setup_writes_one_encoding_whatever_its_stdout_is (cp1252:strict, cp1252:surrogateescape) the issue's first line, #808's probe: exit 0, every stdout and stderr line UTF-8, U+2014 and U+2026 in it as UTF-8
test_bootstrap_runs_the_validators_of_a_project_named_in_japanese (5 STDOUTS) the issue's second line: exit 0, bootstrap ok, and bootstrap: <name> and manifest ok: <name> written alike (escaped under cp1252, ?? under replace, as is under cp932 and UTF-8)
test_make_bootstrap_on_a_project_named_in_japanese_under_cp1252 the same through make bootstrap
test_a_tool_whose_child_inherits_never_changes_its_stdouts_encoding (INHERITING: siblings.py) the issue's third line for siblings.py: the guard reads its run, the run hands no env=, and nothing in the file reconfigures a stream's encoding
test_the_encoding_reader_sees_the_tool_that_changes_it encoding_changes finds setup-project.py's one reconfigure(encoding=...), so the empty answer above is a real one
LAUNCHERS rows [templates/assembly-root/scripts/bootstrap.py] (2) the guard reads the bootstrap's run, and it hands inherited_child_environment()
LAUNCHERS row [setup-project.py] was a strict xfail; passes now, out of NOT_YET

On the base. The new file over db693114's tools: 46 of the 52 new cases fail (10 failed, 36 errors where the copies do not exist yet), and the setup-project.py row, a strict xfail there, fails outright. The 6 that pass there are the bootstrap run's three control encodings (cp1252:replace, cp932:strict, utf-8:strict), the siblings.py pin and the encoding reader (both pin what is already true), and the guard reading the bootstrap's run.

Mutants, one line each, each reverted at once; every one fails at least one new test:

mutant killed by
S1 env=child_environment(capture) if capture else None (the captured child only) test_setup_writes_one_encoding... x2, test_setup_projects_run...[inherited] and [inherited-not-reconfigured]. The LAUNCHERS row passes it (see Not changed)
S2 env= dropped from run the setup-project.py row, the end-to-end run x2, run x3
S3 env=child_environment() (the capture flag ignored) test_setup_projects_run...[captured]
S4 / B2 encoding = sys.stdout.encoding (no canonical name) INHERITED rows ANSI_X3.4-1968 and latin-1, and two no-stdout cases, for that copy
S5 / B3 a raising handler passed on (errors = errors) 7 INHERITED rows for that copy. The end-to-end runs do not see B3: escape_unencodable_stdout has already made the bootstrap's stdout backslashreplace
S6 "replace" dropped from the handler set test_each_copy_keeps_the_handlers...[setup-project.py], 2 INHERITED rows
S7 CAPTURED_CHILD_IOENCODING = "utf-8:replace" the captured copy test, run[captured]
S8 TypeError no longer caught no-encoding
B1 env= dropped from _run the bootstrap row, the bootstrap end-to-end runs under both cp1252 handlers, make bootstrap
B4 the bootstrap's child told utf-8:backslashreplace (the captured rule) 12 INHERITED rows, 4 end-to-end runs, make bootstrap
I1 env=dict(os.environ) on siblings.py's run test_a_tool_whose_child_inherits...[siblings.py]
I2 sys.stdout.reconfigure(encoding='utf-8') in siblings.py's main the same
G1 the setup-project.py row put back in NOT_YET the row (XPASS(strict))

Runs (/usr/bin/python3 -m pytest -q -p no:cacheprovider -o tmp_path_retention_policy=failed, GH_TOKEN unset, an empty GH_CONFIG_DIR), the same 95 files in 8 batches of 12: every file git grep -l -E '_run\(|child_environment|child_env|PYTHONIOENCODING|LAUNCHERS|NOT_YET' -- tests names, every test file naming setup-project.py or the assembly bootstrap.py, the issue's Verify files, test_pinned_copy_text.py, test_family_printed_quoting.py, test_repo_hygiene.py, test_cli_reference.py and test_agents_md_codes.py:

tests/test_validator_child_encoding.py alone: 111 cases before (110 passed, 1 xfailed), 163 after (163 passed).

Not changed

Closes #836

🤖 Generated with Claude Code

Summary by Sourcery

Propagate each parent process's effective output encoding to Python children so setup and bootstrap remain reliable across non-UTF-8 environments.

Bug Fixes:

  • Ensure setup-project.py and assembly-root bootstrap validators use consistent child-process encodings, preventing mixed-encoding output and failures for project names unsupported by the active code page.

Enhancements:

  • Add encoding-aware child environment handling for inherited and captured subprocess output in setup-project.py and for validator subprocesses in the assembly bootstrap script.
  • Expand validator encoding coverage with unit, integration, make-bootstrap, and regression tests, including projects named with Japanese characters.

Tests:

  • Add comprehensive tests validating copied encoding rules, subprocess environments, UTF-8 setup output, bootstrap behavior, and unchanged inheriting-child behavior.

Brett Heap and others added 8 commits October 16, 2026 14:01
…e no bytecode under templates/, the launcher guard reads family.py's siblings child, the quoting-sentence inventory follows an aliased quoter, a suffixless tool and each tool's calling functions, and an S5778 pytest.raises builds its argument first, so each guard fails when what it names breaks (#665)

Four guards held less than they said.

- scripts/render-cli-reference.py: `child_env()` sets PYTHONDONTWRITEBYTECODE=1,
  so the template's `siblings.py` (`from bootstrap import ...` from its own
  directory) leaves no `bootstrap.cpython-312.pyc` under
  templates/family-root/scripts/ when the generator runs its `--help`
  children. No `--help` text changes, so docs/cli.md is not regenerated
  (`render-cli-reference.py --check` still says it is what the generator
  produces). tests/test_cli_reference.py holds the variable in the
  environment and holds that an interpreter started under it writes none.
- tests/test_validator_child_encoding.py: `interpreter_runs` accepts
  `[sys.executable or PYTHON, ...]` as well as `[sys.executable, ...]`, and
  LAUNCHERS gains scripts/family.py with `inherited_child_environment`, so
  dropping `env=` from `family.py siblings`' child (#214) fails the guard.
- tests/test_readme_windows_quoting_sentence.py: the inventory follows
  `from repo_shape import shell_line as joined` (and `joined = shell_line`),
  scans every file `git ls-files` lists that is a `.py` file or has a python
  shebang (the root `bootstrap`), pins the functions that call a host-dialect
  quoter for scripts/family.py and setup-project.py instead of the file
  (shape-doctor.py stays every line, as the sentence says), and exempts the
  bodies of `shell_quoted` and `shell_line` in scripts/repo_shape.py by name,
  not the whole file. No quoting changes: the Git Bash dialect is a separate
  issue.
- tests/test_bump_leg_paste_lines.py: `leg_placement(leg)` is evaluated
  before the `with pytest.raises(LegNotAsFound)` in
  `test_pasting_the_status_line_reads_that_leg` (Sonar S5778), as the test
  above it does.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… as a value and pins each named function's number of host-dialect calls, and the launcher guard reads `run_child` and `run([PYTHON, ...])` and has rows for `src/openreposhape/cli.py` and `setup-project.py`, so a quoter reached by another spelling, one call switched to POSIX in a three-call function, or a Python child started by another spelling no longer slips past the two guards #665 tightened (#759)

tests/test_readme_windows_quoting_sentence.py:
- `host_calls` also returns a host quoter read and not called: an argument or
  keyword of another call (`map(shell_quoted, words)`,
  `functools.partial(shell_line)`, `key=shell_quoted`), a list entry, a
  default or a return value, each under its own function and in source order.
  A call told `'posix'` is `sh`'s, and the alias statement `joined =
  shell_line` is not a use by itself (each use of `joined` is). On the
  shipped tools `host_quoting` is unchanged: nothing hands a quoter on today.
- `NAMED` maps each function to its number of host-dialect uses, and the
  per-function pin compares the counts, so one of `_add_command`'s three
  calls switched to `'posix'` (3 to 2) fails until the sentence and the table
  change together. The failure prints each differing function as (uses in the
  tool, uses in NAMED).

tests/test_validator_child_encoding.py:
- `interpreter_runs` reads a call to `subprocess.run` and its kin, to the
  package's `run_child`, and to `run`, whose argv is `[sys.executable, ...]`,
  `[sys.executable or PYTHON, ...]` or a list whose first word is a name this
  module binds to `sys.executable` (`PYTHON = sys.executable`), spelled out
  or held in a local name. `environment_of` reads the `env=` keyword, or
  `run_child`'s second argument, through a local name.
- `LAUNCHERS` gets `src/openreposhape/cli.py`, whose four `run_child` calls
  each hand `standard.child_environment()` (passes), and `setup-project.py`,
  whose five `run` calls hand no environment at all: that row fails, and is a
  strict `xfail` named in `NOT_YET` with the measurement behind it (under
  `PYTHONIOENCODING=cp1252:strict` nine lines of one local run are not UTF-8,
  the children's code page next to the parent's UTF-8). Fixing `run` turns the
  strict xfail red until the row leaves the table.

Tests only: no shipped file and no pinned file changes.

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

# Conflicts:
#	tests/test_readme_windows_quoting_sentence.py
#	tests/test_validator_child_encoding.py
…de `run` or at every call and not on one that leaves a sixth child bare, and the two guards read a name bound twice, rebound, handed on as a parameter or aliased with an annotation, so the mutants lane 2 found in them no longer survive (#759)

Lane 2's review of #808 (rv808) found that the `setup-project.py` row of the
launcher guard did not flip on the fix its `NOT_YET` reason names, and that the
fix it did see left a sixth child bare. Measured on 84ce67c, with a copy of
`setup-project.py` changed in a scratch clone:

- `env=child_environment()` in `run`'s own `subprocess.run`: the row stayed
  `xfailed`, so the reason was false.
- `env=` at the five calls the guard read: the row went `XPASS(strict)`, though
  `why_the_plan_did_not_go_through` runs the plan again (`run(plan, ...)`, with
  `plan` a parameter) and that sixth child was still bare.

`interpreter_runs` now reads a list handed to a parameter by a caller one level
up, and a name bound in each branch of an `if`; `environment_of` reads the
environment the function it calls hands (`run`'s own call to `subprocess`) when
the call hands none, and the LAST binding of a name above the call. The row
fires on an `env=` in `run` and on an `env` parameter every call passes, and not
on a change that leaves any child bare. `NOT_YET` and the new
`test_setup_project_starts_six_children_and_the_guard_reads_each` say six.

The quoting inventory follows an annotated alias (`q: object = shell_quoted`) at
its calls, reads a `'posix'` argument only after the value the quoter would
quote, and names in `host_calls`' docstring the aliases it counts once.
`interpreter_runs`' docstring lists what it does not see. New cases close the
mutants lane 2 named: `self.quote = shell_line`, `a = b = shell_line`, a value
use in a method, `subprocess.call`, `check_call` and `check_output`, a name
bound to two lists, and a later rebinding of `env`.

Tests only: no shipped file changes.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…hild is told the encoding its words are read in, so a cp1252 stdout gets one encoding from setup, and `make bootstrap` passes a project whose name the code page cannot hold (#836)

`setup-project.py`'s `main` reconfigures its stdout and stderr to UTF-8,
and `run` started every child with no `env=`, so a Python child took its
streams' encoding from its own environment. Under
`PYTHONIOENCODING=cp1252:strict` or `cp1252:surrogateescape` (Windows'
ANSI code page on a stdout that is not the console), a `--yes
--local-remote-dir` run exited 0 with 9 of its 129 stdout lines in cp1252
among UTF-8 ones, and the plan run again to read why it was refused, the
one captured child, wrote cp1252 into a pipe `run` decodes as UTF-8. The
assembly root's `scripts/bootstrap.py` started the validators the same
way, so on a project whose `name:` the code page cannot hold,
`validate-manifest.py` raised `UnicodeEncodeError` and `make bootstrap`
ended `bootstrap FAILED: manifest (exit 1)`: #254's defect, in a pinned
copy.

- `setup-project.py`: `child_environment(capture)`, #254's rule as
  `scripts/shape_materialize.py` writes it in
  `inherited_child_environment` and `captured_child_environment`. `run`
  passes it as `env=`, so all six Python children get it, and the next
  one will: `self_bootstrap`, `check_names`, `plan_and_confirm`,
  `why_the_plan_did_not_go_through` (the captured one), `scaffold` and
  `clone_and_bootstrap`. It holds a copy because it runs before any
  checkout of the standard exists.
- `templates/assembly-root/scripts/bootstrap.py`:
  `inherited_child_environment()`, a copy of the library's, which `_run`
  passes. A project does not carry `shape_materialize.py`, and the skew
  rule (`tests/test_pinned_copy_text.py`) keeps a pinned script from
  importing a new name out of `repo_shape.py`. This is a shape bump: every
  project re-pins the copy.
- `tests/test_validator_child_encoding.py`: the `setup-project.py` row
  leaves `NOT_YET`, and `LAUNCHERS` gains the assembly `bootstrap.py`.
  `COPIES` holds both copies to the library's answer for every stream
  `INHERITED` names. `run` is shown to tell each child what it is read
  in, both tools run end to end under cp1252, and `INHERITING` pins that
  `siblings.py`'s child inherits an environment its code page agrees
  with: measured, it needs no change.

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

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR makes child-process output encoding explicit across setup-project.py and the assembly-root bootstrap, preventing mixed UTF-8/cp1252 output and validator failures for unrepresentable project names, while adding comprehensive behavioral and static-analysis tests and extending the Windows quoting guard.

Sequence diagram for consistent child-process encoding

sequenceDiagram
    participant Setup as setup-project.py
    participant Child as Python child
    participant Stream as stdout or captured pipe

    Setup->>Setup: main reconfigures stdout and stderr to UTF-8
    Setup->>Setup: child_environment(capture)
    Setup->>Child: subprocess.run(env=child_environment(capture))
    Child->>Stream: write using PYTHONIOENCODING
    alt captured child
        Setup->>Stream: decode as UTF-8 with errors=replace
    else inherited child
        Child->>Stream: use stdout encoding and safe error handler
    end
Loading

Sequence diagram for encoding-safe assembly bootstrap validators

sequenceDiagram
    participant Bootstrap as scripts/bootstrap.py
    participant Validator as Validator
    participant Stdout as Bootstrap stdout

    Bootstrap->>Bootstrap: inherited_child_environment()
    Bootstrap->>Validator: subprocess.run(env=inherited_child_environment())
    Validator->>Stdout: write using bootstrap stdout encoding
    Stdout-->>Bootstrap: consistent validator output
    Bootstrap-->>Stdout: bootstrap status and validation messages
Loading

Flow diagram for captured and inherited child environment selection

flowchart TD
    Start[run child process] --> Capture{capture output?}
    Capture -->|yes| Captured[Set PYTHONIOENCODING=utf-8:backslashreplace]
    Capture -->|no| Encoding[Read sys.stdout.encoding]
    Encoding --> Usable{Usable encoding?}
    Usable -->|no| Original[Keep environment unchanged]
    Usable -->|yes| Handler{Handler never raises?}
    Handler -->|yes| Preserve[Keep stream handler]
    Handler -->|no| Safe[Use backslashreplace]
    Preserve --> Inherited[Set encoding and handler in child environment]
    Safe --> Inherited
    Captured --> Launch[Launch subprocess]
    Original --> Launch
    Inherited --> Launch
Loading

File-Level Changes

Change Details Files
Propagate the effective output encoding to every child process launched by the project setup script.
  • Added a standalone child-environment helper that mirrors the library’s inherited and captured I/O encoding rules, including codec normalization and non-raising error-handler fallback.
  • Updated the common subprocess wrapper to pass the environment to all children, including the captured plan diagnostic run.
  • Clarified documentation describing why parent stream reconfiguration is not inherited by Python children.
setup-project.py
Make the assembly-root bootstrap configure validator children consistently with its own stdout.
  • Added a pinned standalone copy of the inherited-child environment logic without importing newer library code.
  • Passed that environment to validator subprocesses so non-ASCII project names succeed under incompatible code pages.
  • Established the corresponding shape bump for scaffolded projects.
templates/assembly-root/scripts/bootstrap.py
Expand encoding and launcher validation to cover the new copied helpers and subprocess paths.
  • Added unit, subprocess, end-to-end, and make-bootstrap coverage for inherited and captured encodings, absent streams, handler behavior, cp1252, and Japanese project names.
  • Added checks that each helper copy matches the library rule and that launcher analysis recognizes setup-project and assembly bootstrap child-process spellings.
  • Removed the setup-project launcher from the pending-failure list and added the assembly bootstrap helper row.
tests/test_validator_child_encoding.py
Strengthen the Windows quoting guard’s static analysis and account for use counts.
  • Recognized annotated aliases and host-quoting functions passed as values, while excluding POSIX-directed uses and unsupported alias forms.
  • Tracked host-dialect use counts per function and updated the expected setup-project and family-root inventories.
tests/test_readme_windows_quoting_sentence.py

Assessment against linked issues

Issue Objective Addressed Explanation
#836 Make every Python child started through setup-project.py's run use an encoding compatible with the parent’s output: UTF-8 for inherited output after reconfiguration, and UTF-8 with backslashreplace for captured output decoded as UTF-8. ✅
#836 Have the assembly-root bootstrap.py pass its validators an environment matching the bootstrap’s stdout encoding and non-raising error handler, so names unrepresentable in the code page do not make validation fail. ✅
#836 Update the child-encoding guard and tests to cover the fixed launchers and verify that the family-root siblings.py, which intentionally inherits its environment without changing stdout encoding, remains consistent. ✅

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-17T21:26:10Z) Independent review of #851 (#836) at 90cb256b, for lane openRepoShape-2: READY

I found nothing at MUST or SHOULD level. Every finding below is a NIT, plus one hand-off to lane 5 about #808's guard.

Findings

Ruling on the shape bump: it is acceptable in v1.1.1.

Checked and true

  • Both copies match bump-leg.py (a real bump) and update-shape.py apply run the project's own validators as children that cannot print its display name on a cp1252 stdout, so a project named 日本 is refused and rolled back as bump-leg-validators-red or update-validators-red under PYTHONIOENCODING=cp1252:surrogateescape, and the refusal blames the validators #254's rule exactly. The rule is scripts/shape_materialize.py:256-325. My parity script compared the library, the setup-project.py copy and the bootstrap.py copy over 207 streams (17 encodings × 12 handlers, plus 3 odd streams): 0 differences. The captured child is equal too.
  • No Python child is left out.
    • Every child in setup-project.py starts through run, whose one subprocess.run is at :372. That covers all six children the writer names.
    • The bootstrap's only interpreter start is _run at :150.
  • Issue line 1, measured.
    • Head: 129 lines and 0 that are not UTF-8, under both cp1252:strict and cp1252:surrogateescape.
    • Base db693114: 9 such lines (24 31 38 45 64 79 80 100 102).
    • With --into …/日本work, head writes 0 non-UTF-8 lines and 日本 five times as UTF-8. Base writes 9 non-UTF-8 lines and one \u65e5.
  • Issue line 2, a project named 日本.
    • Head exits 0 under cp1252:strict, cp1252:surrogateescape, cp1252:replace, cp932, utf-8 and an unset variable.
    • Base exits 1 under both cp1252 handlers with bootstrap FAILED: manifest (exit 1).
    • Head also passes, and base fails, under:
      • LC_ALL=C PYTHONUTF8=0;
      • PYTHONUTF8=1 with cp1252:strict;
      • an empty PYTHONIOENCODING;
      • :strict.
    • cp65001, latin-1:namereplace and cp1252:xmlcharrefreplace behave the same on head and base.
    • A name holding a lone surrogate gives the same result on head and base.
    • A non-UTF-8 byte in --into shows the same way on head and base.
  • The new tests on the base's two tools: 11 failed and 36 errors (46 new cases plus the setup-project.py row), 116 passed. This matches the PR body.
  • Mutants: 31 one-line mutants, 30 killed, G4 survives (NIT 3).
  • Sonar:
    • Cognitive complexity is 3 for child_environment and 2 for inherited_child_environment.
    • There are no composite asserts and no string literal used 3 or more times.
    • The repository has no sonar-project.properties and no Sonar step in a workflow, so templates are not excluded. The duplicated part of the rule is about 10 lines (≈60 tokens by hand count), below Sonar's 100-token minimum for a duplicate.
  • Docs: no --help, README, handbook, AGENTS.md or docs/cli.md text changes. test_cli_reference, test_agents_md_codes and test_repo_hygiene pass.
  • Merges: all clean (details in Part 2).

Could not verify

Lane: openreposhape-2 (openRepoShape-2)

@sonarqubecloud

Copy link
Copy Markdown

@brettheap
brettheap marked this pull request as ready for review October 18, 2026 02:35
@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 3 days and 16 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@brettheap
brettheap merged commit ef439fd into main Oct 18, 2026
5 checks passed
@brettheap
brettheap deleted the fix/836-child-environment branch October 18, 2026 02:36
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