Repository navigation
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
Conversation
…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>
…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>
…uards) as the stacked base of #836
…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>
Reviewer's GuideThe 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 encodingsequenceDiagram
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
Sequence diagram for encoding-safe assembly bootstrap validatorssequenceDiagram
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
Flow diagram for captured and inherited child environment selectionflowchart 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
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-2, 2026-10-17T21:26:10Z) Independent review of #851 (#836) at 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
Could not verify
Lane: openreposhape-2 (openRepoShape-2) |
|
|
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 |



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 bootstrappasses a project whose name the code page cannot holdLane: openreposhape-2 (openRepoShape-2)
Claim: #836 (comment)
Stacked on
Lane 5's tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard reads
run_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808 (tests: the quoting-sentence inventory misses a quoter passed as a value and pins functions where it means calls, and the launcher guard has no row forsrc/openreposhape/cli.pyorsetup-project.py#759),fix/759-quoting-launcher-guards, head08a6ed39634f9cf34edcd2a453adde1bd682fb23(checked on origin before the merge). Merged with--no-ffasdb693114: "Merge openRepoShape-5's tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard readsrun_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808 head 08a6ed3 (fix/759-quoting-launcher-guards) as the stacked base of setup-project.py'srunand the assembly root's bootstrap.py_runstart Python children with no environment, so a cp1252 stdout gets two encodings from setup and a name it cannot hold endsmake bootstrapinvalidate-manifest.py#836". No conflict: tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard readsrun_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808 touches onlytests/test_validator_child_encoding.pyandtests/test_readme_windows_quoting_sentence.py.tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard reads
run_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808 has landed as677f9bfa, the squash of that same head:git diff 08a6ed39 677f9bfaover its two files is empty. Merging08a6ed39again was "Already up to date". The branch then merged that squash, then main:61eceb41: "Merge lane 5's tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard readsrun_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808 squash 677f9bf (its final head 08a6ed3 is merged at db69311)". Its one conflict wastests/test_validator_child_encoding.py. The squash's side of it is08a6ed39's file byte for byte, and the branch's side is that file plus this PR's change, so the branch's side was kept.git diff 90cb256b 61eceb41over tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard readsrun_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808's two files is empty. The merge also brought main fromd66a9054to677f9bfa, including bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824 (below).dad55d2d: "Merge origin/main (c100843) into fix/836-child-environment after tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard readsrun_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808 landed as 677f9bf", with no conflict. It brought shape-doctor.py: a conflicted shape pin's NEXT takes the side whose pin reads, and names no line where no side's pin reads, so run as printed it no longer leaves the doctor naming the same NEXT again (#752) #779 (shape-doctor.pyandtests/test_doctor_pin_restore_merges.py), which touches none of this PR's files.Review the commit after
db693114:90cb256b.git diff origin/main dad55d2dis that commit's change. It covers the same three files, with the same 470 changed lines at-U0: the two-U0line listsdiffempty, and the-U0patch-ids are equal. Only the context intemplates/assembly-root/scripts/bootstrap.pydiffers: bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824 added lines above this PR's hunks, which now sit 6 and 10 lines lower.Overlaps, each
git merge-tree --write-treeof the headdad55d2dwith theirs, all clean:bootstrap REFUSED, exit 2, an unreadable upstream path, cache or pin is no answer and a reftable leg's cherry-pick or revert is read from its ref store, so no traceback or ended pick follows (#764) #815 (bootstrap.py (the assembly root's): aSHAPE_UPSTREAM_PATHdirectory nobody may enter ends in a traceback, a validator's refusal is reported asFAILED, exit 1, a reftable leg's cherry-pick goes unseen, anddocs/cli.mdsays bootstrap places each leg AT the pin with no condition #764, now05a1ebb3, wasf6f318d1), which edits the assemblybootstrap.py;MSYSTEMset) the lines that follow the host are quoted forsh, through one question,host_shell_dialect, so the doctor's lines, setup's family lines andfamily.py'sNEXT,switch -c,addandcdlines paste whole in the shell that printed them, and README's Windows sentence says so #821 (7581ffb9), setup-project.py: a refused value names the check it failed, a project name over 95 characters is refused before step (4), and the prompt takes the spaces off a typed name, soAtlasno longer hides in step (4) (#777) #811 (050abea6) and setup-project.py: a project name typed at the prompt is checked as--projectis and an empty line says no name was typed, so../x,a/b,..and--helpare refused by name, not by an--idhint, a help page or a probe write (#729) #756 (now892ffd97, wasb3c77cc2), which editsetup-project.pyfar fromrun.Lane 4's bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824 (bootstrap.py and repo_shape.py: a leg on a branch whose name is not UTF-8 makes every later
make bootstrapend in a UnicodeDecodeError, and a character the filesystem encoding lacks still ends step (a) in a traceback #710) has landed as0dee22b3, and this branch holds it through677f9bfa. Itsbootstrap.pymerged without a conflict._runis still that file's only Python child (templates/assembly-root/scripts/bootstrap.py:160); bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824's git children go through_git_says, andPYTHONIOENCODINGdoes not reach git. Whichever of this PR and bootstrap.py: a validator's exit 2 endsbootstrap REFUSED, exit 2, an unreadable upstream path, cache or pin is no answer and a reftable leg's cherry-pick or revert is read from its ref store, so no traceback or ended pick follows (#764) #815 lands second re-gates against the other. The helper's first placement, directly afterVALIDATORS, conflicted with bootstrap.py: a validator's exit 2 endsbootstrap REFUSED, exit 2, an unreadable upstream path, cache or pin is no answer and a reftable leg's cherry-pick or revert is read from its ref store, so no traceback or ended pick follows (#764) #815'sVALIDATOR_REFUSED = 2at the same spot. It sits below_runinstead, directly above bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824's_try_git.What changed
1.
setup-project.py: every child ofrunis told what its words are read in.mainreconfigures stdout and stderr to UTF-8, and a child does not inherit areconfigure: a Python child takes its streams' encoding from its own environment. So underPYTHONIOENCODING=cp1252:strictorcp1252: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 piperundecodes as UTF-8.child_environment(capture)is bump-leg.py (a real bump) and update-shape.pyapplyrun 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 asbump-leg-validators-redorupdate-validators-redunderPYTHONIOENCODING=cp1252:surrogateescape, and the refusal blames the validators #254's rule, asscripts/shape_materialize.pywrites it ininherited_child_environmentandcaptured_child_environment: an inherited child is told the stdout's own encoding (UTF-8 aftermain) with its handler where that handler never raises, andbackslashreplacewhere it does; a captured child is toldCAPTURED_CHILD_IOENCODING(utf-8:backslashreplace), whatrundecodes. No usable stdout leaves the environment as it is.runpassesenv=child_environment(capture)in its ownsubprocess.run, so all six Python children get it:self_bootstrap(the checkout's own copy of the file),check_names(the naming validator),plan_and_confirm(the plan),why_the_plan_did_not_go_through(the plan again, captured),scaffoldandclone_and_bootstrap(scripts/bootstrap.py). The next child added goes throughruntoo. git and gh get it as well;PYTHONIOENCODINGis read by Python alone.runis what clones one.main's comment and the module docstring said a child's output "travels through this process's streams"; an inherited child's does not, and both now say so.2. The assembly root's
scripts/bootstrap.py: the validators are told what bootstrap's stdout is._runpassesenv=inherited_child_environment(), a copy of the library's: a project does not carryshape_materialize.py, and a pinned script that imported a new name out ofrepo_shape.pywould be refused atupdate-shape.py apply(update-accept-local-missing-name, #536,tests/test_update_shape_kept_helper.py), so the copy keepsrepo_shape.pyuntouched, as the validators keep their own_shown. This is a shape bump: every project re-pinsscripts/bootstrap.py. No digest is committed anywhere in the repository; a scaffold computescontracts/shape-pin.yamlfrom the bytes it copies.3.
tests/test_validator_child_encoding.py. Thesetup-project.pyrow leavesNOT_YET(which is now empty, its comment kept),LAUNCHERSgainstemplates/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>, aghstub that exits 97 first on PATH,GH_TOKEN=bogus, an emptyGH_CONFIG_DIR):A new
bootstrap.pycopied by hand into a project scaffolded before it is refusedshape-copy-driftby that project's own pin, as it should be: the change reaches a project throughupdate-shape.py apply.Tests
tests/test_validator_child_encoding.pytest_each_copy_tells_an_inherited_child_what_the_library_does(2 copies x 14INHERITEDstreams)COPIES:setup-project.py'schild_environment, the bootstrap'sinherited_child_environment) builds the very environmentshape_materialize.inherited_child_environmentbuilds, read from the stdout it runs undertest_each_copy_leaves_the_environment_alone_with_no_stdout(2 x 3)PYTHONIOENCODINGleft as it wastest_each_copy_keeps_the_handlers_the_library_keeps(2)HANDLERS_THAT_NEVER_RAISEequals the library's_HANDLERS_THAT_NEVER_RAISEtest_setup_projects_copy_tells_a_captured_child_what_the_library_doeschild_environment(capture=True)equalscaptured_child_environment(), under an operator'scp1252:stricttest_setup_projects_run_tells_each_child_what_its_words_are_read_in(3)runitself: an inherited child is toldutf-8:replace(the stdoutmainleaves) orcp1252:backslashreplace(one it could not reconfigure); a captured oneutf-8:backslashreplace, andrunreadsmanifest ok: 日本backtest_setup_writes_one_encoding_whatever_its_stdout_is(cp1252:strict, cp1252:surrogateescape)test_bootstrap_runs_the_validators_of_a_project_named_in_japanese(5STDOUTS)bootstrap ok, andbootstrap: <name>andmanifest ok: <name>written alike (escaped under cp1252,??underreplace, as is under cp932 and UTF-8)test_make_bootstrap_on_a_project_named_in_japanese_under_cp1252make bootstraptest_a_tool_whose_child_inherits_never_changes_its_stdouts_encoding(INHERITING:siblings.py)siblings.py: the guard reads its run, the run hands noenv=, and nothing in the file reconfigures a stream's encodingtest_the_encoding_reader_sees_the_tool_that_changes_itencoding_changesfindssetup-project.py's onereconfigure(encoding=...), so the empty answer above is a real oneLAUNCHERSrows[templates/assembly-root/scripts/bootstrap.py](2)inherited_child_environment()LAUNCHERSrow[setup-project.py]xfail; passes now, out ofNOT_YETOn 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 thesetup-project.pyrow, a strictxfailthere, fails outright. The 6 that pass there are the bootstrap run's three control encodings (cp1252:replace,cp932:strict,utf-8:strict), thesiblings.pypin 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:
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]. TheLAUNCHERSrow passes it (see Not changed)env=dropped fromrunsetup-project.pyrow, the end-to-end run x2,runx3env=child_environment()(the capture flag ignored)test_setup_projects_run...[captured]encoding = sys.stdout.encoding(no canonical name)INHERITEDrowsANSI_X3.4-1968andlatin-1, and two no-stdout cases, for that copyerrors = errors)INHERITEDrows for that copy. The end-to-end runs do not see B3:escape_unencodable_stdouthas already made the bootstrap's stdoutbackslashreplace"replace"dropped from the handler settest_each_copy_keeps_the_handlers...[setup-project.py], 2INHERITEDrowsCAPTURED_CHILD_IOENCODING = "utf-8:replace"run[captured]TypeErrorno longer caughtno-encodingenv=dropped from_runmake bootstraputf-8:backslashreplace(the captured rule)INHERITEDrows, 4 end-to-end runs,make bootstrapenv=dict(os.environ)onsiblings.py's runtest_a_tool_whose_child_inherits...[siblings.py]sys.stdout.reconfigure(encoding='utf-8')insiblings.py'smainsetup-project.pyrow put back inNOT_YETXPASS(strict))Runs (
/usr/bin/python3 -m pytest -q -p no:cacheprovider -o tmp_path_retention_policy=failed,GH_TOKENunset, an emptyGH_CONFIG_DIR), the same 95 files in 8 batches of 12: every filegit grep -l -E '_run\(|child_environment|child_env|PYTHONIOENCODING|LAUNCHERS|NOT_YET' -- testsnames, every test file namingsetup-project.pyor the assemblybootstrap.py, the issue's Verify files,test_pinned_copy_text.py,test_family_printed_quoting.py,test_repo_hygiene.py,test_cli_reference.pyandtest_agents_md_codes.py:db693114(a scratch clone): 6540 passed, 5 skipped, 2 xfailed, nothing red;90cb256b: batches 1 to 7 (84 files): 5932 passed, 5 skipped, 1 xfailed, the same counts as the base's batches 1 to 7, nothing red. Batch 8 (the last 11 files: the sixtest_update_shape*.py,test_validate_pins_lockstep_lines.py,test_validate_pins_without_git.py,test_validator_child_encoding.py,test_windows_paths.py,test_workflow_concurrency.py): 661 passed, nothing red; its base run was 608 passed, 1 xfailed, so thesetup-project.pyxfail is now a pass and the 52 new cases are the rest. All 8 batches, 95 files: 6593 passed, 5 skipped, 1 xfailed (the one xfail left is batch 7's, as on the base).test_validator_child_encoding.pywas run on its own on these bytes: 163 passed.61eceb41(tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard readsrun_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808's squash and bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824 merged), 18 files in 2 batches:test_validator_child_encoding.py,test_readme_windows_quoting_sentence.py,test_repo_hygiene.py,test_cli_reference.py,test_agents_md_codes.py,test_git_output_decoding.py(it reads every tool'ssubprocesscalls, these two files' among them),test_openreposhape_package.py(it names achild_environment, the package's own), the issue's other Verify files (test_setup_project_py.py,test_setup_sh.py,test_bootstrap_without_git.py,test_bootstrap_after_bump.py), four moretest_bootstrap_*.py(bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824's two among them),test_update_shape_kept_helper.py,test_pinned_copy_text.pyandtest_stdout_encoding.py: 1421 passed, 1 skipped (test_stdout_encoding.py:286, Windows only), nothing red;dad55d2d(shape-doctor.py: a conflicted shape pin's NEXT takes the side whose pin reads, and names no line where no side's pin reads, so run as printed it no longer leaves the doctor naming the same NEXT again (#752) #779 merged):test_validator_child_encoding.py(still 163 cases),test_readme_windows_quoting_sentence.py,test_repo_hygiene.py,test_cli_reference.py,test_agents_md_codes.pyand shape-doctor.py: a conflicted shape pin's NEXT takes the side whose pin reads, and names no line where no side's pin reads, so run as printed it no longer leaves the doctor naming the same NEXT again (#752) #779'stest_doctor_pin_restore_merges.py: 376 passed, nothing red.tests/test_validator_child_encoding.pyalone: 111 cases before (110 passed, 1 xfailed), 163 after (163 passed).Not changed
templates/family-root/scripts/siblings.py: measured, no defect, no change.run_member_bootstrap(:567) run on a member undercp1252:strict,cp1252:surrogateescapeandutf-8:strict, with a member carrying the oldbootstrap.pyand then the new: every line of the run is in the stdout's own encoding, the parent and the member both at<encoding>:backslashreplace. The child is given the very environment the parent's stdout was configured from, andescape_unencodable_stdoutchanges only the handler, which the member's bootstrap changes the same way. The only failure, with the old member, is that member's own validator: this PR's second line. It has noLAUNCHERSrow: a row there names a helper the environment must name, and this child is right because it is handed none. So it is inINHERITING, whose test reads the file's AST and not its bytes, so the next edit to the pinned copy is no re-pin of the test.run_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808's guard logic.interpreter_runs,environment_ofand the row test are as tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard readsrun_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808 left them. One limit, measured: the row reads the helper's name in the source text, so it passesenv=child_environment(capture) if capture else None(S1), the captured-child-only fix lane 2's rv808 SHOULD warned of, with five children bare. The end-to-end run and theruntest kill it. A guard that reads both branches of a conditionalenv=is tests/: the quoting-sentence inventory sees a quoter handed on as a value and pins each function's host-dialect calls, and the launcher guard readsrun_childandrun([PYTHON, ...]), with rows forcli.pyandsetup-project.py(#759) #808's to decide.scripts/repo_shape.py: no helper moved there (the skew rule, and repo_shape.py, shape_materialize.py and adopt-project.py: a value the YAML reader refuses for opening with an indicator is named by its line and says to double-quote it or reword it,plan --org @xnames--orgagain under the reader's own remedy, andchecknames one badtracking_branchonce (#708) #817, repo_shape.py, setup-project.py, shape-doctor.py, README and the handbook: under Git Bash (MSYSTEMset) the lines that follow the host are quoted forsh, through one question,host_shell_dialect, so the doctor's lines, setup's family lines andfamily.py'sNEXT,switch -c,addandcdlines paste whole in the shell that printed them, and README's Windows sentence says so #821, repo_shape.py strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it, and validate-manifest, bootstrap, bump-leg and update-shape refuse a branch holding a character that does not print or is white space (#715) #823 and validate-pins.py and repo_shape.py: a pin, the manifest or a shape copy the gate cannot reach or read is refused by name, relative to the root, once and with its cause's advice, and every name and path it prints is spelled (#712) #827 edit it).templates/family-root/scripts/bootstrap.pystartsmakein members (:517), not the interpreter, and never changes its stdout's encoding either.--helptext, README, handbook,AGENTS.md,AGENTS-shape.mdordocs/cli.mdchanges.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:
Enhancements:
Tests: