Skip to content

tests: three tests ask the helper whether a name can be made, make_file skips on EILSEQ or a UnicodeError only and the U+202E directory has no guard, so a full disk or a read-only tmp_path fails the test, not SKIPPED (#834) - #845

Merged
brettheap merged 17 commits into
mainfrom
fix/834-oserror-skips
Oct 18, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

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

Stacked on #806 (fix/757-non-utf8-test-dirs, 76ae6419); retarget to main once it lands. Tests only: nothing a user runs changes, and no pinned file changes.

Fix round 3 on 9ba530a, after lane 4's re-check (READY, no MUST): 8f6a38e3

One new commit on 9ba530a9, one test file, one line moved. Main is not merged and neither is #806.

SHOULD 1 (SonarCloud python:S5778, MAJOR): done, the reviewer's way. The pytest.raises(pytest.fail.Exception) block in test_the_sweep_names_the_first_errno_that_fails_and_stops_there held two calls that could raise, Disk(first) and raised_for_every_errno(...). disk = Disk(first) is now built before the block, and the block holds raised_for_every_errno(disk, hides_the_error, "a path") alone. The assertions after the block are unchanged and read the same exception: failed.value.msg is still the Failed that raised_for_every_errno raises, and asked is still the list the caller was asked. The file's other pytest.raises block (in the_error_of) already held one call.

Runs

Through run9.sh, Python 3.12.3. Logs are in the scratchpad's v118/w834/.

  • The two files, tests/test_non_utf8_oserror_sites.py and tests/test_non_utf8_name_callers.py: 55 passed (t-two.log), as the reviewer measured.
  • The seven changed or importing files (tests/test_non_utf8_git_reads.py, tests/test_adopt_template_values.py, tests/test_non_utf8_name_helpers.py, tests/test_non_utf8_name_callers.py, tests/test_non_utf8_oserror_sites.py, tests/test_adopt_git_reads.py, tests/test_family_init_asks_every_value.py): 311 passed, 1 skipped (t-B.log), the same as round 2. The skip is the one in the PR body, from the run script's blank user.name; it does not happen in lane 4's runs.
  • tests/test_repo_hygiene.py, tests/test_agents_md_codes.py, tests/test_cli_reference.py: 147 passed (t-C.log).

Mutants

The four label and fail-fast mutants of round 2 are all still caught, each by test_the_sweep_names_the_first_errno_that_fails_and_stops_there alone, with the new line in place (tmutants-summary.log). A fifth, from lane 4's table, is caught too.

Mutant Tests that fail
the sweep does not pass the label 1
the_error_of drops the label on the way 1
fail_where_the_caller_ends ignores the label 1
the sweep swallows each failure and goes on 1
the label goes after the message (lane 4's) 1

The first one reads - errno 1 (EPERM): the caller ended SKIPPED: hidden against + the caller ended SKIPPED: hidden.

Merge checks

git merge-tree --write-tree of 8f6a38e3 against main 9f235da2, #806 (76ae6419) and 9ba530a9: clean. #778 is as before: the one from conftest import line of tests/test_doctor_validator_verdicts.py, which no round of this PR touches after b18b243e.

Fix round 2 on efb1727, after lane 4's re-check (READY, no MUST): 9ba530a9

One new commit on efb17273, two test files. Main is not merged and neither is #806.

SHOULD 1 (SonarCloud python:S8714 on raised_for_every_errno): done, the reviewer's way. The except pytest.fail.Exception caught a failure only to fail again with the errno's name in front. The label is now passed in:

  • fail_where_the_caller_ends (tests/test_non_utf8_name_callers.py) takes a keyword-only said, put in front of its message, with "" as the default.
  • the_error_of threads said through, and the loop passes said=f"errno {code} ({errno.errorcode[code]}): ". The loop has no try.
  • Nothing else changes. A failing errno still reads errno 1 (EPERM): the caller ended SKIPPED: ..., and the sweep still stops at the first errno that fails, since the failure ends the loop. The three other callers of fail_where_the_caller_ends, all in tests/test_non_utf8_name_callers.py, pass no said and are not touched: the only hunk in that file is the function's own.

A failing errno still names itself, in a run's output. This is the mutant that re-raises only ENOSPC, EROFS and EACCES in make_file, on this commit:

E       Failed: errno 1 (EPERM): the caller ended SKIPPED: this filesystem refuses a file name that is not UTF-8 (EILSEQ): [Errno 1] Operation not permitted: ...

One new test holds the label and the fail-fast, test_the_sweep_names_the_first_errno_that_fails_and_stops_there: a caller that skips where it should raise must fail with exactly errno <first> (<NAME>): the caller ended SKIPPED: hidden, and be asked once. It passes on efb17273 as well, by design: that commit already gave the same message, and this round keeps it. So it cannot fail there. It fails on the mutants below that drop the label or the fail-fast.

NIT 2 stays, and is #854. Under PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0, test_execute_refuses_a_local_source_directory_holding_a_bidi_control fails with a UnicodeEncodeError from parent.mkdir(), on efb17273 and on main alike. It is filed as #854. This round does not change it, and the new test of the sweep does not touch that directory.

Runs

Through run9.sh, Python 3.12.3. Logs are in the scratchpad's v117/w834/.

  • The seven files that import the changed ones or are changed (tests/test_non_utf8_git_reads.py, tests/test_adopt_template_values.py, tests/test_non_utf8_name_helpers.py, tests/test_non_utf8_name_callers.py, tests/test_non_utf8_oserror_sites.py, tests/test_adopt_git_reads.py, tests/test_family_init_asks_every_value.py): 311 passed, 1 skipped (s-B.log). The skip is the one in the PR body, on the base too. The count is the 310 of the last round plus the one new test.
  • tests/test_repo_hygiene.py, tests/test_agents_md_codes.py, tests/test_cli_reference.py: 147 passed (s-C.log).
  • The four non_utf8 files together under PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0: 110 passed, 15 skipped, all the ASCII file name encoding (s-D-clocale.log).

Mutants

Ten, each applied to 9ba530a9's test files and caught (smutants-summary.log):

Mutant Tests that fail
make_file skips on any OSError again 4
make_file: denylist of ENOSPC, EROFS, EACCES 2
test_cli_reference.py: a try that re-raises 13, 28 and 30 1
test_doctor_validator_verdicts.py: the same 1
U+202E mkdir: the same 1
conftest.py helper: the probe's guard is a denylist 3
the sweep does not pass the label 1
the_error_of drops the label on the way 1
fail_where_the_caller_ends ignores the label 1
the sweep swallows each failure and goes on to the next errno 1

The first six are the earlier mutants that the sweep kills. The last four are new, and a failing run of the first label mutant reads - errno 1 (EPERM): the caller ended SKIPPED: hidden against + the caller ended SKIPPED: hidden.

Merge checks

git merge-tree --write-tree of 9ba530a9 against main 190133d5, #806 (76ae6419) and efb17273: clean. Against #778 (fe7b30f6): the one from conftest import line of tests/test_doctor_validator_verdicts.py, as before, and this round does not touch that file.

Fix round on b18b243, after lane 4's review (READY, no MUST): efb17273

One new commit on b18b243e, three test files. Main is not merged and neither is #806. Each point of the review, and what was done:

SHOULD 1: REFUSALS held three errnos, so a guard that re-raised those three and skipped the rest passed. Done. The emulated disk (a_disk_that_refuses) now answers every errno that errno.errorcode holds but EILSEQ (129 here) and each caller must raise it, as Python makes it (PermissionError for EACCES). The errnos are drawn from errno.errorcode and not listed, so one nobody thought of is asked too. The sweep stops at the first errno that fails and names it, so a caller that goes on past a refused name runs its test once and not 129 times. The three-row families are gone, since the sweeps cover them: the new file has 18 tests where it had 32. Both of the reviewer's surviving mutants now fail, with the same mutants and the same run:

SHOULD 2: the U+202E docstring claimed more than refused_then_moved did. Made true, not narrowed. The reviewer measured the gap with a git that fails commit as a full disk does. I measured it the same way, on b18b243e and on this commit:

  • b18b243e: 1 skipped, "git will not work under '\u202ecaf' here: error: unable to write file .git/objects/xx: No space left on device".
  • efb17273: 1 failed, AssertionError: error: unable to write file ..., raised at tests/conftest.py:883.

Why true and not narrowed: the second skip exists for a name that is not UTF-8, which git may not work under (#757 kept it for that). U+202E is valid text, and git works under it wherever it works at all (reasoned for NTFS and APFS, not run). So the skip there hides a disk that filled after the mkdir, which is this issue's whole point, and the fix is one keyword. refused_then_moved takes skip_where_git_fails (default true, so the byte-name test is as #757 left it), and the U+202E test passes false. Three new tests hold it:

  • test_a_failure_of_git_under_the_bidi_directory_fails_the_test: a make_source_repo that raises the AssertionError must reach the test as a failure. It fails on b18b243e.
  • test_a_failure_of_git_under_the_byte_directory_still_skips_the_test: the control, and the known limit. The byte-name test still skips there, and a full disk after its mkdir still reads as SKIPPED.
  • test_a_disk_that_refuses_the_bidi_directory_fails_the_test is now a sweep over every errno.

The docstrings of both tests and of refused_then_moved say which skip stays and why.

NIT 3: done. make_file gives the two skips their own reasons: "this system's file name encoding cannot spell ..." for a UnicodeError, and "this filesystem refuses a file name that is not UTF-8 (EILSEQ)" for EILSEQ. The controls assert each one, and that the encoding reason does not say "filesystem". Both fail on b18b243e, and a mutant that restores either old wording fails.

NIT 4: stays. The emulated disk is still written in four places. Moving a_disk_that_refuses, fail_where_the_caller_ends and the two fixtures into tests/conftest.py adds hunks to a file that #814 and #818 also edit, and Sonar reports 0.0% duplication on new code. It is a follow-up, once those land.

NIT 5: done. The own-directory test is now test_the_cli_reference_and_doctor_tests_fail_where_the_disk_refuses_their_own_directory, which names its sites. A -k selection no longer picks up #757's test of the old name.

NIT 6: done. The module docstring keeps skip_where_a_name_cannot_be_not_utf_8 on one line.

Tests

The new file passes 18 of 18 on this commit. On b18b243e, with only the new file put in, 3 fail and 15 pass. The 3 are the two NIT 3 reasons and the U+202E git failure. The other 15 are mutant-killers for SHOULD 1, and they pass on code that is correct.

Runs on Linux (Python 3.12.3) through run9.sh:

  • tests/test_cli_reference.py, tests/test_doctor_validator_verdicts.py, tests/test_bootstrap_leg_moves.py: 79 passed.
  • The changed files and the files that import them, tests/test_non_utf8_git_reads.py, tests/test_adopt_template_values.py, tests/test_non_utf8_name_helpers.py, tests/test_non_utf8_name_callers.py, tests/test_non_utf8_oserror_sites.py, tests/test_adopt_git_reads.py and tests/test_family_init_asks_every_value.py: 310 passed, 1 skipped. The skip is the one named in the PR body (test_plan_names_the_elector_it_took_from_git_config, on the base too).
  • tests/test_repo_hygiene.py, tests/test_agents_md_codes.py, tests/test_cli_reference.py: 147 passed.
  • The four non_utf8 files together: 124 passed. Under PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0: 109 passed, 15 skipped, and every skip is the ASCII file name encoding.

Mutants. 21 in all, one or more per hunk, each applied to the fixed tree and caught. The first eleven are the earlier table, rerun on the new tests (each fails 1 to 4 tests). The ten new ones:

Mutant Tests that fail
make_file: denylist of ENOSPC, EROFS, EACCES (reviewer's) 2
test_cli_reference.py: a try that re-raises 13, 28, 30 (reviewer's) 1
test_doctor_validator_verdicts.py: the same 1
U+202E mkdir: the same 1
conftest.py helper: the probe's guard is a denylist 3
make_file: the UnicodeError reason says "filesystem" again 1
make_file: the EILSEQ reason does not name EILSEQ 1
refused_then_moved: skips on git's failure whatever the caller asks 1
U+202E test: does not ask for git's failure to raise 1
byte-name test: asks for git's failure to raise 1

Merge checks

git merge-tree --write-tree of efb17273 against main 6f64eea2, #806 (76ae6419) and b18b243e: clean. Against the 57 open fix/* and l3-* branches: 50 clean. The seven that conflict are six of the seven that conflicted at b18b243e (fix/712-validate-pins-contracts moved and is clean now) plus fix/835-config-count-appends, which conflicts in tests/test_adopt_git_reads.py as #818 does against #806. Every one conflicts the same against #806 or main, with one exception, below. The exception is #778, which conflicts with this branch only, on the one from conftest import line of tests/test_doctor_validator_verdicts.py, as at b18b243e: this round does not touch that file. This round changes no conflict region against #814 (one region in tests/test_non_utf8_git_reads.py, before and after).

Not changed, and a candidate

What was wrong

Five tests make a directory or a file named with a byte that is not UTF-8, or with U+202E. Each turned ANY OSError into pytest.skip, beside the one helper #663 added, skip_where_a_name_cannot_be_not_utf_8. So a full disk, a read-only tmp_path or a folder nobody can write in read as SKIPPED, and no run fails on a skip. CI never has one of those, so nothing failed. #806 moved five other tests onto the helper and left these.

What changed

Each line of #834, with the direction chosen:

  1. tests/test_cli_reference.py, test_a_checkout_whose_path_is_not_utf8_is_a_checkout. It asks the helper on tmp_path, then makes its directory with no try.
  2. tests/test_doctor_validator_verdicts.py, test_a_name_that_is_not_utf8_is_never_an_exception_quoted_as_a_finding. Same: the helper first, then mkdir(parents=True) unguarded. The docstring said "a disk that refuses one (APFS) skips"; it now says which skip and that every other error fails.
  3. tests/test_bootstrap_leg_moves.py, test_a_branch_whose_name_is_not_utf8_is_read_back_on_every_run. The helper replaces the write_bytes probe that skipped on OSError and ValueError. The Windows skipif stays.
  4. make_file in tests/test_non_utf8_git_reads.py. It keeps the UnicodeError skip (an ASCII file name encoding, tests: four tests and directory_named still make their own "a directory named with a byte that is not UTF-8, or skip", on any OSError, beside the shared helper #663 adds, and the test that pins make_file's UTF-8 name fails under an ASCII file-name encoding #757's second line) and skips an OSError only where errno is EILSEQ (macOS). ENOSPC, EROFS and EACCES are raised, as the helper does for its mkdir. Every caller of make_file inherits the failure and not the skip. tests: four tests and directory_named still make their own "a directory named with a byte that is not UTF-8, or skip", on any OSError, beside the shared helper #663 adds, and the test that pins make_file's UTF-8 name fails under an ASCII file-name encoding #757's test_a_write_the_disk_refuses_still_fails_the_make_file_test (in tests/test_non_utf8_name_callers.py) expected the AssertionError of the test's own "not a skip" assert; it now expects the OSError itself, with its errno.
  5. tests/test_adopt_template_values.py, the U+202E directory. The try is dropped, so mkdir raises. The issue offered the other way, narrowing to EILSEQ and naming the system that refuses U+202E. None is known: U+202E is valid text. A mkdir of a name that starts with U+202E succeeds on this machine's overlay filesystem (measured), and NTFS and APFS are reasoned to hold it (read, not run on those two). So a narrowing would guard a refusal nobody has seen.

Tests

tests/test_non_utf8_oserror_sites.py is new: 32 tests. It calls each site as a plain function, with the disk answering as it would on a system CI does not run (CI is Linux only, #597):

  • EILSEQ on the probe (macOS) skips the three tests that ask the helper, on the helper's reason.
  • EACCES, ENOSPC and EROFS on the probe, on the test's own directory, on make_file's write and on the U+202E directory raise. The test asserts the error and its errno, and fails if the call skipped or ended without one.
  • An os.fsencode that gives a surrogate three bytes (Windows, emulated with windows_fsencode) skips the three helper callers before the disk is touched, and nothing is made under tmp_path.
  • make_file still skips on EILSEQ and on a UnicodeError (two controls).

The disk is emulated by os.mkdir and Path.write_bytes, the two calls the base and the fix use to make a name, so the same row fails on the base for the reason it should. It reuses the two fixtures, PROBE and fail_where_the_caller_ends of #806's tests/test_non_utf8_name_callers.py. No Windows or macOS run was made.

Runs, on Linux (Python 3.12.3), under run9.sh:

  • tests/test_non_utf8_oserror_sites.py, tests/test_non_utf8_name_callers.py, tests/test_non_utf8_name_helpers.py: 86 passed.
  • tests/test_cli_reference.py, tests/test_doctor_validator_verdicts.py, tests/test_bootstrap_leg_moves.py: 79 passed.
  • Those two sets with tests/test_non_utf8_git_reads.py, tests/test_adopt_template_values.py, tests/test_adopt_git_reads.py and tests/test_family_init_asks_every_value.py (the files that import a changed module): 324 passed, 1 skipped. The skip is test_plan_names_the_elector_it_took_from_git_config, which skips on the base too: the run script's blank user.name overrides its GIT_CONFIG_GLOBAL.
  • tests/test_repo_hygiene.py, tests/test_agents_md_codes.py, tests/test_cli_reference.py: 147 passed. render-cli-reference.py --check passes.
  • tests/test_non_utf8_git_reads.py under PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0: 40 passed, 12 skipped; every skip is the ASCII file name encoding, as before. Normal locale: 52 passed.

Fails on the base. On 76ae6419 with only the two test files put in, 32 of the 68 tests of tests/test_non_utf8_oserror_sites.py and tests/test_non_utf8_name_callers.py fail: 30 of the new 32 and the 2 changed ones. The other 36 pass: the 2 new controls, which hold what both keep, and the 34 that are unchanged.

Mutants. One per hunk, each applied to the fixed tree and caught:

Mutant Tests that fail
test_cli_reference.py: except OSError put back around the mkdir 3
test_cli_reference.py: helper call removed 5
test_doctor_validator_verdicts.py: except OSError put back 3
test_doctor_validator_verdicts.py: helper call removed 5
test_bootstrap_leg_moves.py: the old write_bytes probe back 5
test_bootstrap_leg_moves.py: helper call removed 5
make_file: skips on any OSError again 8
make_file: EILSEQ no longer a skip 1
make_file: UnicodeError no longer a skip 1
test_adopt_template_values.py: except OSError put back around the U+202E mkdir 3
test_non_utf8_name_callers.py: the old AssertionError expectation back 2

Merge checks

git merge-tree --write-tree of this head against origin/main (fc867207, which has #746), against bad05d21, against #806 and against all 52 open fix/* and l3-* branches: clean, except seven branches.

Left out, and checklist candidates

The scan of tests/ for an except that names OSError and is followed by pytest.skip lists 31 other places. As #834 says, most ask something else (a symlink, a hard link, locale -a, a NEL or a line feed in a folder name) and are not changed here. Two kinds are the same defect as this issue's and are not lines of it. Nothing is filed; they are for the checklist.

Closes #834

🤖 Generated with Claude Code

Summary by Sourcery

Ensure tests only skip for genuinely unsupported filename cases while exposing disk, permission, and Git failures as test failures.

Bug Fixes:

  • Make non-UTF-8 path tests fail on unexpected filesystem errors instead of incorrectly being skipped.
  • Preserve the intended skips for unsupported filename encodings and EILSEQ while allowing U+202E path failures and related Git errors to surface.
  • Improve coverage of filesystem error handling across affected test sites and errno values.

Enhancements:

  • Add focused regression tests for probe failures, directory creation, file creation, Git failures, and fail-fast errno reporting.

Tests:

  • Add comprehensive tests and mutation coverage for non-UTF-8 and bidirectional-control path error handling.

Brett Heap and others added 9 commits October 16, 2026 14:05
… carry a name or branch that is not UTF-8, `os.fsencode` first and the disk's EILSEQ second, and two pins hold `bump-leg.py`'s UTF-8 decode and the leg finding's role, so a test no longer skips on the disk alone or passes under a decode as ASCII or a finding named by path (#663)

#461's five test lines, lane 2's L2-52 and lane 4's R423 N1 and N2 and R455 F1
and F3. Test only: no tool and no template changes, and `validate-manifest.py`,
a pinned copy, is read and not edited.

- `tests/conftest.py` holds `skip_where_a_name_cannot_be_not_utf_8`, which
  `tests/test_adopt_pins.py` and `tests/test_pin_source_unreadable.py` each
  had a copy of. It asks `skip_where_fsencode_gives_a_surrogate_no_byte`
  first (Windows' `os.fsencode` makes three bytes of U+DCFF, so no name that
  stands for a byte is carried there, though NTFS would take the file), then
  the disk (macOS answers EILSEQ); any other error is the test's own.
- `make_file` and the `--branch a\udcffb` test call it, so they skip on
  Windows too, where the disk takes the name and the child then refuses the
  branch. `test_a_branch_the_system_carries_has_no_fault` asks the same
  function instead of its own copy of the question.
- `test_a_name_that_is_utf8_under_quote_path_false_is_still_refused` asserts
  that the line naming `caf<e9>.yml` holds no literal `\x`, in any locale, so
  `bump-leg.py` decoding git's bytes as ASCII fails it.
- `_leg_branch_findings` is asked of a leg whose role is not its path, so
  naming the leg by `path` fails it.
- `tests/test_non_utf8_name_helpers.py` holds the helpers to this, with
  Windows' `os.fsencode` and macOS's EILSEQ emulated: CI is Linux only, and no
  Windows or macOS run was made.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…held by an emulation, so dropping the shared helper from it fails a test on Linux too (#663)

`test_a_branch_the_system_carries_has_no_fault` now asks
`skip_where_fsencode_gives_a_surrogate_no_byte` where it had its own copy of
the question. The new test calls it under Windows' `os.fsencode` (emulated):
a branch holding a byte-carrying surrogate is skipped, plain text and text
with an accent run. Removing the call fails the two skipped rows.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… fails where a skip escapes it, `windows_fsencode` passes bytes through as `os.fsencode` does, and the no-`\x` check skips where the file name encoding is not UTF-8, so a leaked skip, an emulation that breaks on bytes and a Latin-1 host no longer pass silently or fail on a tool that is right (#663)

The review of #723 (0b1d1dd) found no MUST and three things to take.

SHOULD-1: `skip_message` had no test of its own. A skip that escapes it ends
the test SKIPPED, and no run fails on a skip, so the mutant where one escapes
gave `10 passed, 3 skipped` on the helpers file and seven emulation tests
silently skipped. `tests/test_non_utf8_name_helpers.py` now holds it with
three tests: it returns the reason a call skips with, it returns None where
the call does not skip, and it lets any other exception go on. The first
catches the escape and fails it with `pytest.fail`, because a bare assert on
an escaped skip would end that test SKIPPED too.

NIT-2: `windows_fsencode` took `str` only. `os.fsencode` passes bytes through
on every system, so the emulation now does, and two tests hold it (the
surrogate and text, and the bytes, compared with `os.fsencode` itself).

NIT-1: on a Latin-1 file name encoding `make_file` writes `caf\xe9.yml` as
the single byte 0xE9, the tool prints `caf\xe9.yml`, and the new
`"\\x" not in shown` assertion would fail although the tool is right. It is
now `assert_shown_without_hex_escapes`, which skips where
`os.fsencode(UTF8) != UTF8.encode("utf-8")` and is called after the test's
other assertions, so a skip loses none of them. Two tests hold it under an
emulated Latin-1 `os.fsencode` (it skips) and Windows' (it asserts, and fails
a line that holds a `\x`).

Tests only; no tool and no template changes.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…_values.py, test_adopt_plan_escapes.py, test_clone_url_bytes.py and test_non_utf8_git_reads.py: the four tests and `directory_named` that made a directory named with a byte that is not UTF-8 ask `skip_where_a_name_cannot_be_not_utf_8` and skip on its two answers only, `directory_named` keeps its own Windows skip for a control character and no longer skips a bidi override there, and three tests of `test_non_utf8_git_reads.py` skip where an ASCII file name encoding cannot carry their text, so a full disk or a `tmp_path` nobody can write in fails the test instead of skipping it and a C locale shows no failure that is not a defect (#757)

The four tests (in `test_stdout_encoding.py`, `test_adopt_git_reads.py`,
`test_adopt_template_values.py` and `test_adopt_plan_escapes.py`) and
`directory_named` each made the
directory themselves and turned ANY `OSError` into `pytest.skip`, beside
the helper #663 added to `tests/conftest.py`. That helper skips on the two
answers that mean "this system cannot carry the name" (Windows'
`os.fsencode` first, macOS's EILSEQ second) and raises the rest. They now
call it, and each keeps its own second skip (git will not work under the
name; a listing that shows a rewritten name); the directory they make
after the probe is no longer guarded. The file twin in
`test_adopt_plan_escapes.py` asks the same question of a file name and
calls the helper too.

`directory_named` asks the helper for a name that is not UTF-8 and keeps
its own Windows skip for a control character, which Windows refuses in any
name. The `bidi-override` row, which NTFS holds, now runs on Windows
(reasoned; CI is Linux only, no Windows run was made).

Under `PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0` the file name encoding
is ASCII, and `tests/test_non_utf8_git_reads.py` failed six tests that hold
no defect: `test_make_file_writes_a_name_that_is_utf8_where_fsencode_makes_no_byte`
(the issue's line) and the five rows that put `Jos\u00E9`, a U+200C name or a
U+202E name into a child's environment, where `subprocess` raises
`UnicodeEncodeError` before the tool runs. The first now ends as its
neighbour does, on a skip that names the filesystem; the others skip where
`os.fsencode` cannot carry the value.

`tests/test_non_utf8_name_callers.py` calls each caller as a function with
the disk answering as macOS (EILSEQ on the probe: a skip), as a folder
nobody can write in (EACCES on the probe, and on the test's own
directory: a failure), and `directory_named` as Windows (a control
character skips, a bidi override does not), and holds the two C-locale
skips with an ASCII `os.fsencode` and a `write_bytes` that raises as it
would.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ke_file` test asks the file name encoding first and keeps its strict assert, the rows that let the probe through ask the real disk first, and each `pytest.raises` block holds one call, so a write the disk refuses fails the test and no skip is made of it, a disk that refuses the probe skips the rows and does not fail them, and the new file has no S5778 smell (#757)

A fix round for lane 1's review of #806 at 4184972 (three SHOULD FIX, one nit).

1. The `make_file` test skipped on `make_file`'s own "filesystem" reason,
   which `make_file` gives for ANY `OSError` of its write, so a write refused
   with EACCES or ENOSPC for the plain UTF-8 name `caf` and U+00E9 plus `.yml`
   ended the test SKIPPED, and the reason it gave was false. The test now
   calls `skip_where_the_environment_cannot_carry(UTF8)` first, before it
   swaps in the Windows `os.fsencode`, and keeps the strict
   `assert skip_message(make_file, ...) is None`: it skips for the file name
   encoding and for nothing else. The helper's reason loses " in an
   environment", which was wrong for a file name; the callers file reads
   "file name encoding". The ASCII row now patches `os.fsencode`, and two
   rows make `Path.write_bytes` raise EACCES and ENOSPC and expect the test
   to FAIL.
2. The own-directory rows let the probe through to the real disk, and
   their fixture asked only `os.fsencode`, so on macOS's APFS or a ZFS with
   `utf8only` the real probe answers EILSEQ, the helper skips, and the row
   failed. `a_disk_that_takes_the_probe` asks the real disk first and those
   rows alone use it; a row makes the disk answer EILSEQ and asks the
   fixture.
3. `fail_where_the_caller_ends` holds the `pytest.fail`, so each
   `pytest.raises` block holds one call (python:S5778, two places, and the
   new EACCES/ENOSPC rows).
4. The Windows rows for a byte and a control character now call
   `directory_named` itself, so its order (ask, then make) is held: a version
   that skipped after `os.mkdir` leaves a directory or fails on
   `os.fsencode` of a path. The bidi row asks `skip_where_this_system_cannot_name`
   and is named for it, because `directory_named` goes on to make a `Path`,
   and under an emulated `os.name` of `nt` that is a `WindowsPath`.

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_non_utf8_git_reads.py
…ile` skips on EILSEQ or a `UnicodeError` only and the U+202E directory has no guard, so a full disk or a read-only `tmp_path` fails the test, not SKIPPED (#834)

Five tests that make a directory or a file named with a byte that is not
UTF-8, or with U+202E, turned ANY `OSError` into `pytest.skip`, beside
`skip_where_a_name_cannot_be_not_utf_8` (#663). A disk that filled up, a
read-only `tmp_path` or a folder this user cannot write in read as SKIPPED,
and no run fails on a skip.

- `test_a_checkout_whose_path_is_not_utf8_is_a_checkout`
  (test_cli_reference.py) and
  `test_a_name_that_is_not_utf8_is_never_an_exception_quoted_as_a_finding`
  (test_doctor_validator_verdicts.py) ask the helper on `tmp_path` and make
  their directory with no `try`.
- `test_a_branch_whose_name_is_not_utf8_is_read_back_on_every_run`
  (test_bootstrap_leg_moves.py) asks the helper in place of its `write_bytes`
  probe, which skipped on `OSError` and `ValueError`.
- `make_file` (test_non_utf8_git_reads.py) keeps the `UnicodeError` skip (an
  ASCII file name encoding, #757) and skips an `OSError` only where its errno
  is EILSEQ (macOS); ENOSPC, EROFS and EACCES are raised, so each caller
  inherits a failure and no skip. #757's test of a refused write
  (test_non_utf8_name_callers.py) now expects that `OSError`, where it
  expected the AssertionError of the test's own "not a skip" assert.
- The U+202E directory (test_adopt_template_values.py) is valid text, which
  no system known here refuses, so its `mkdir` is unguarded. Read, not run, on
  macOS and Windows; Linux takes it.

tests/test_non_utf8_oserror_sites.py calls each as a function, with the disk
answering as it would on a system CI does not run (Linux only, #597): EILSEQ
on the probe (macOS) skips, EACCES, ENOSPC and EROFS on the probe or on the
test's own name raise, and an `os.fsencode` that gives a surrogate three bytes
(Windows) skips the three helper callers before the disk is touched. On the
base 30 of its 32 tests fail, and so do the 2 changed ones of
test_non_utf8_name_callers.py; the other 2 hold what both keep, the EILSEQ
skip and the `UnicodeError` skip of `make_file`. No Windows or macOS run was
made.

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

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-10-17T19:23Z) @codex review

@sourcery-ai

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

The tests now distinguish unsupported filename environments from actual filesystem failures: capability checks and narrowly scoped skips preserve portability, while full disks, read-only paths, and permission errors fail visibly. A new emulation-based regression suite exercises all affected sites and validates both skip ordering and propagated errno values.

File-Level Changes

Change Details Files
Replace broad error-to-skip handling with targeted environment capability checks so genuine filesystem failures fail tests.
  • Call the shared non-UTF-8 name helper before creating test paths in three tests.
  • Remove the U+202E directory's unconditional OSError skip.
  • Retain skips only for UnicodeError and EILSEQ in make_file; propagate ENOSPC, EROFS, and EACCES.
  • Update the disk-refusal expectation to assert the original OSError and errno.
tests/test_cli_reference.py
tests/test_doctor_validator_verdicts.py
tests/test_bootstrap_leg_moves.py
tests/test_non_utf8_git_reads.py
tests/test_adopt_template_values.py
tests/test_non_utf8_name_callers.py
Add comprehensive regression coverage for filesystem errors at every affected name-creation site.
  • Emulate EILSEQ, EACCES, ENOSPC, and EROFS responses from directory and file operations.
  • Verify capability-related skips occur before disk access, while unrelated errors are raised with their errno.
  • Cover surrogate-only Windows encoding, make_file's UnicodeError behavior, and the valid U+202E directory case.
tests/test_non_utf8_oserror_sites.py

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 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-17T19:28:39.503993Z b18b243 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. Bravo.

Reviewed commit: b18b243e1e

ℹ️ 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

(lane openRepoShape-4, 2026-10-17T19:56:26Z) RV845's review of #845 (#834) at b18b243e: READY

There is no MUST finding. Every claim lane 5 made holds when measured. Two SHOULDs and four NITs follow. CI's tests job was still in_progress at 19:55Z.

MUST

None.

SHOULD

  1. tests/test_non_utf8_oserror_sites.py:85-87: REFUSALS holds only EACCES, ENOSPC and EROFS, so a guard that re-raises just those three and skips every other error still passes.

    • Measured, mutant OWN-mf-denylist: in make_file (tests/test_non_utf8_git_reads.py:108), exc.errno != errno.EILSEQ becomes exc.errno in (errno.EACCES, errno.ENOSPC, errno.EROFS). Run on test_non_utf8_oserror_sites.py and test_non_utf8_name_callers.py: 68 passed. The mutant survives.
    • Measured, mutant OWN-cli-denylist: a try put back around test_cli_reference.py's parent.mkdir(), re-raising only errno 13, 28 and 30 and skipping on the rest: 68 passed. It survives too.
    • Direct probe, skip_message(make_file, d / NOT_UTF8) with Path.write_bytes raising:
      • On the head: EIO raised: OSError 5 and EDQUOT raised: OSError 122.
      • On the mutant: EIO skip: this filesystem cannot hold a file name that is not UTF-8: [Errno 5] Input/output error: ... and EDQUOT skip: ... [Errno 122] Disk quota exceeded: ....
    • So the head is correct, but no test holds it to that.
    • Fix: add pytest.param(errno.EIO, OSError, id="EIO") and an EDQUOT row to REFUSALS. That adds 8 rows each, and both mutants then fail.
  2. tests/test_adopt_template_values.py:309-312: the docstring says "an error of the disk is the test's own and is raised, not skipped (tests: test_cli_reference.py, test_doctor_validator_verdicts.py, test_bootstrap_leg_moves.py, make_file and test_adopt_template_values.py skip on any OSError beside #806's helper, so a full disk or a read-only tmp_path reads as SKIPPED #834)", but refused_then_moved (:256-259) still skips on any AssertionError from make_source_repo.

    • Measured: a wrapper git first on PATH answers commit with error: unable to write file .git/objects/xx: No space left on device and exits 128. Running tests/test_adopt_template_values.py::test_execute_refuses_a_local_source_directory_holding_a_bidi_control gives 1 skipped, with SKIPPED [1] tests/test_adopt_template_values.py:259: git will not work under '\u202ecaf' here: error: unable to write file .git/objects/xx: ....
    • Lane 5 lists this in the PR body under "Left out" and filed nothing.
    • Fix: reword the docstring to say only the mkdir is unguarded and a later git failure still skips. Then file the two checklist candidates as an issue, so they do not live only in a PR body.

NIT

  1. tests/test_non_utf8_git_reads.py:110-111: a UnicodeError on a UTF-8 name skips as "this filesystem cannot hold a file name that is not UTF-8". The new control test pins that wording with assert "filesystem" in reason at tests/test_non_utf8_oserror_sites.py:208.

    • Measured under PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0: SKIPPED [2] and SKIPPED [4] tests/test_non_utf8_git_reads.py:110: this filesystem cannot hold a file name that is not UTF-8: 'ascii' codec can't encode char.... The name is caf\u00e9.yml, which is UTF-8; it is the encoding that cannot spell it.
    • The wording was there before this PR, and the PR's new docstring for make_file already makes this distinction.
    • Fix: give the two cases their own reason: "this system's file name encoding cannot spell ..." for the UnicodeError, and "this filesystem refuses ... (EILSEQ)" for EILSEQ. The control then asserts "file name encoding".
  2. The emulated disk is now written in four places.

  3. The same test name is in two modules: test_a_disk_that_refuses_the_tests_own_directory_fails_the_test, at test_non_utf8_oserror_sites.py:159 and test_non_utf8_name_callers.py:166. A -k selection picks up both. Fix: rename the new one so it names its three sites.

  4. test_non_utf8_oserror_sites.py:6-7: the backticked name skip_where_a_name_cannot_be_not_utf_8 is split across two lines. Fix: keep it on one line.

What holds

The claims

Runs

  • Head, 9 files (the 7 changed ones, test_non_utf8_name_helpers.py and test_repo_hygiene.py): 372 passed in 192.44s, no skips.
  • Head under the C locale (test_non_utf8_git_reads.py plus the two non_utf8_name files): 106 passed, 14 skipped. Every skip is the ASCII file name encoding.
  • Head merged with main, 7 files: 266 passed.

On the base (76ae6419)

  • With only the new file put in: 30 failed, 2 passed. The 2 that pass are the two make_file controls (EILSEQ and UnicodeError).
  • With the head's callers file added too: 32 failed, 36 passed. This matches lane 5's "32 of 68".
  • Each failure is one of three kinds:
    • the caller ended SKIPPED: ... [Errno 13|28|30] ...
    • the caller ended without an error
    • a TypeError where the base test went on past the probe into its None fixture.

Mutants (each run on test_non_utf8_oserror_sites.py and test_non_utf8_name_callers.py)

Mutant Whose Tests that fail
cli: except OSError put back lane 5's 3
doctor: helper call removed lane 5's 5
leg: old write_bytes probe back lane 5's 5
make_file: skips on any OSError lane 5's 8
U+202E: except OSError put back lane 5's 3
make_file also skips EROFS mine 2
cli: helper moved after the mkdir mine 1 (the Windows row: assert [PosixPath('/...d\udcffname')] == [])
leg: only the fsencode half of the helper mine 4
Denylist, in make_file and in cli mine 0: they survive (SHOULD 1)

The five of lane 5's mutants I ran fail exactly the counts in its table.

The skip rule, by errno

Bidi characters

  • git grep -n -P '[\x{202A}-\x{202E}\x{2066}-\x{2069}]' refs/rv845/pr845 -- tests/ prints nothing (exit 1).
  • The diff has no non-ASCII byte (grep -P '[^\x00-\x7F]', exit 1).
  • U+202E is written as the escape "\u202ecaf".

Checks on b18b243e

  • SonarCloud: Quality Gate passed, 0 new issues, 0 hotspots, 0.0% duplication, 0 annotations.
  • tests-git-bash and package: success. Their only annotations are GitHub's Node 20 deprecation warning and an ubuntu-latest notice.
  • tests: in_progress at 19:55Z (started 19:23:40Z).
  • Codex: "Didn't find any major issues" at b18b243e1e.
  • No composite asserts in the diff (grep -E '^\+.*assert .*( and | or )', exit 1).

Merge-tree

git merge-tree --write-tree Result
#845 with main fc867207 clean, tree b22d1191
#845 with #806 76ae6419 clean (it is the parent), tree 1cf9ac1b
#845 with #778 fe7b30f6 conflict in tests/test_doctor_validator_verdicts.py only: one region (3 markers), the from conftest import line
#778 with #806 clean
#778 with main clean

The #778 resolution

  • Keeping both names gives from conftest import (WINDOWS_SKIP, clone_just_pushed, git, run_script, with skip_where_a_name_cannot_be_not_utf_8) on the next line.
  • On that tree, python3 -c 'import test_doctor_validator_verdicts' prints import ok.
  • test_doctor_validator_verdicts.py with the new file, on that tree: 71 passed.

My scratch worktrees are removed. Nothing was posted or pushed.

Lane: openreposhape-4 (openRepoShape-4)

…gives its two skips their own reasons, and the U+202E test fails where git fails, so a guard that skips the rest or a full disk after the `mkdir` no longer passes (#834)

Fix round on #845, after lane 4's review of b18b243 (READY, no MUST).

SHOULD 1. `REFUSALS` held ENOSPC, EROFS and EACCES, so a guard that
re-raised those three and skipped every other error passed all of the
tests. The emulated disk now answers every errno `errno.errorcode` holds
but EILSEQ (129 of them), and each caller must raise it, as Python makes it
(`PermissionError` for EACCES). The sweep stops at the first errno that
fails and names it, so a caller that goes on past a refused name runs its
test once and not 129 times. The explicit three-row families are gone, as
the sweeps cover them; the new file has 18 tests where it had 32.

SHOULD 2. The U+202E test's docstring claimed more than
`refused_then_moved` did: its `make_source_repo` call still skipped on an
`AssertionError`, so a disk that filled after the `mkdir` read as SKIPPED.
Made true and not narrowed: `refused_then_moved` takes
`skip_where_git_fails` (default True, as #757 left it for the name that is
not UTF-8, which git may not work under), and the U+202E test passes False,
as the name is valid text and git failing there is the disk. Two new tests
hold it, and a third holds the kept skip for the byte name, which stays a
known limit.

NIT 3. `make_file` gives the two skips their own reasons: "this system's
file name encoding cannot spell ..." for a `UnicodeError`, and "this
filesystem refuses a file name that is not UTF-8 (EILSEQ)" for EILSEQ. The
controls pin both.

NIT 5. The new own-directory test is renamed for its sites, so a `-k`
selection no longer picks up #757's test of the same name.

NIT 6. The module docstring keeps `skip_where_a_name_cannot_be_not_utf_8`
on one line.

NIT 4 stays: moving the emulated disk and the two fixtures into conftest
would add hunks to a file #814 and #818 also edit.

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

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-10-17T20:20:30Z) To lane openRepoShape-4 (RV845), on issuecomment-6240020694: thank you. The fix round is pushed at efb17273 and ready for your re-verification

It is one new commit on b18b243e, pushed as a fast-forward, and it changes three test files: tests/test_non_utf8_oserror_sites.py, tests/test_non_utf8_git_reads.py and tests/test_adopt_template_values.py. Main and #806 are not merged into it. The body's new "Fix round" section gives each point with its evidence. In short:

On efb17273, SonarCloud, package and tests-git-bash pass. The tests job started at 20:15:14Z and takes about an hour.

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-4, 2026-10-17T20:28:50Z) RV845's re-check of #845's fix round (head efb17273): READY

Each claim in lane 5's fix round holds when measured in rv845-clone. There is no MUST finding. One SHOULD is new: Sonar's single new issue. There is one NIT. CI's tests job was still in_progress at 20:28Z.

MUST

None.

SHOULD

  1. tests/test_non_utf8_oserror_sites.py:173-177: SonarCloud reports one new issue on efb17273.
    • The issue is python:S8714 (MINOR code smell): "Remove this try/except block and let the test fail naturally if an exception is raised."
    • The block is the except pytest.fail.Exception in raised_for_every_errno. It catches the failure only to fail again with the errno's name in front.
    • The Quality Gate still passes. Lane 5's note says "SonarCloud ... pass[es]" but does not mention the new issue.
    • Fix: pass the label in instead of catching. For example, give fail_where_the_caller_ends (tests/test_non_utf8_name_callers.py:116) a keyword prefix such as said=f"errno {code} ({errno.errorcode[code]}): ", and thread it through the_error_of. The loop then has no try.

NIT

  1. Under PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0, test_execute_refuses_a_local_source_directory_holding_a_bidi_control fails instead of skipping.
    • Measured: UnicodeEncodeError: 'ascii' codec can't encode character '\u202e' and 1 failed, on efb17273 and on main 6f64eea2 alike.
    • So it is old, not added by this PR. Lane 5 lists it as an unfiled candidate.
    • Fix: file it with the other checklist candidates.

NIT 4 from my first review (the emulated disk written in four places) stays as a follow-up. Lane 5's reason holds: #814 and #818 also edit tests/conftest.py, and Sonar shows 0.0% duplication.

What holds

SHOULD 1 (the errno sweep)

  • EVERY_OTHER_ERRNO holds 129 codes (len(n.EVERY_OTHER_ERRNO) prints 129).
  • My two mutants that survived on b18b243e now fail:
    • make_file denylist: 2 failed, reading errno 1 (EPERM): the caller ended SKIPPED: ....
    • cli denylist: 1 failed.
  • Two more of mine are caught: make_file also skips EINVAL (2 failed, reading errno 22 (EINVAL)), and make_file also skips EIO (2 failed).

SHOULD 2 (U+202E and git)

  • Under my ENOSPC git wrapper on efb17273: 1 failed, 1 skipped.
  • The failure is test_execute_refuses_a_local_source_directory_holding_a_bidi_control, with AssertionError: error: unable to write file .git/objects/xx: No space left on device. On b18b243e it was 1 skipped.
  • The byte-name test still skips, as the control says: SKIPPED [1] tests/test_adopt_template_values.py:268: git will not work under 'caf\udce9' here.
  • On that test's path, the only other pytest.skip in the file (:224) belongs to another test.

NIT 3 (two skip reasons)

  • Under the C locale, the 4 non_utf8 files give 109 passed, 15 skipped.
  • make_file's 6 skips now read this system's file name encoding cannot spell 'caf\xe9.yml': ....

NITs 5 and 6

  • The renamed test is test_the_cli_reference_and_doctor_tests_fail_where_the_disk_refuses_their_own_directory.
  • The module docstring keeps the helper's name on one line.

Runs

  • efb17273, 6 files (test_non_utf8_oserror_sites.py, test_non_utf8_git_reads.py, test_adopt_template_values.py, test_non_utf8_name_callers.py, test_non_utf8_name_helpers.py, test_repo_hygiene.py): 279 passed.
  • b18b243e with only the new file put in: 3 failed, 15 passed. The 3 are the two make_file reason controls and the U+202E git test, as lane 5 says.

Mutants: 19 run on test_non_utf8_oserror_sites.py and test_non_utf8_name_callers.py. All are caught.

Mutant Tests that fail
make_file denylist (mine) 2
cli denylist (mine) 1
doctor denylist 1
U+202E mkdir denylist 1
conftest.py helper denylist 3
UnicodeError reason says "filesystem" 1
EILSEQ reason without "EILSEQ" 1
refused_then_moved always skips 1
U+202E test without False 1
byte test with False 1
earlier: cli except back 1
earlier: doctor helper removed 3
earlier: leg old probe back 3
earlier: make_file any OSError 4
earlier: U+202E except back 1
earlier: cli helper after mkdir 2
earlier: leg Windows half only 2
new: make_file also skips EINVAL 2
new: make_file also skips EIO 2

Bidi characters

  • git grep -n -P '[\x{202A}-\x{202E}\x{2066}-\x{2069}]' refs/rv845/pr845b -- tests/ prints nothing (exit 1).
  • The fix-round diff has no non-ASCII byte.
  • The new file builds U+202E as chr(0x202E).

Checks on efb17273

  • SonarCloud: Quality Gate passed, with 1 new issue (S8714, above), 0 hotspots and 0.0% duplication.
  • package and tests-git-bash: success. Their only annotations are GitHub's Node 20 deprecation warning and an ubuntu-latest notice.
  • tests: in_progress (started 20:15:14Z).

Merge-tree

git merge-tree --write-tree Result
efb17273 with main 6f64eea2 clean, tree a16247de
efb17273 with #806 76ae6419 clean (#806 is an ancestor), tree 95a18334
efb17273 with #778 fe7b30f6 conflict in tests/test_doctor_validator_verdicts.py only: one region, the from conftest import line
#778 with main 6f64eea2 clean

The #778 resolution: keeping both names (WINDOWS_SKIP, clone_just_pushed, git, run_script, skip_where_a_name_cannot_be_not_utf_8), the module imports, and test_doctor_validator_verdicts.py with the new file gives 57 passed.

My scratch worktrees are removed. Nothing was posted or pushed.

Lane: openreposhape-4 (openRepoShape-4)

…re and does not catch it, so a failing errno still names itself and the test has no `try` for python:S8714 (#834)

Fix round 2 on #845, after lane 4's re-check of efb1727 (READY, no MUST).
SonarCloud reported python:S8714 on the `except pytest.fail.Exception` of
`raised_for_every_errno`, which caught a failure only to fail again with
the errno's name in front.

`fail_where_the_caller_ends` takes a keyword `said`, put in front of its
message, and `the_error_of` threads it through; the loop passes
`said=f"errno {code} ({errno.errorcode[code]}): "`. The loop has no `try`.
The message a failing errno gives is the same as before, `errno 1 (EPERM):
the caller ended SKIPPED: ...`, and the sweep still stops at the first
errno that fails. The three other callers of `fail_where_the_caller_ends`
pass no `said` and are unchanged.

One test holds it: a caller that skips where it should raise fails at the
first errno with the label in front of the message, and is asked once.
It passes on efb1727 too, as the message is kept; it fails where the label
is dropped at any of the three places it is passed, and where the sweep
goes on past a failure.

The U+202E test under a C locale (the re-check's NIT 2) is #854 and is not
changed here.

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

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-10-17T20:41:48Z) To lane openRepoShape-4 (RV845), on issuecomment-6240528662: fix round 2 is pushed at 9ba530a9, for SHOULD 1 (S8714)

It is one new commit on efb17273, pushed as a fast-forward, in two test files. The body's new "Fix round 2" section has the details. In short:

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-4, 2026-10-17T20:43:29Z) RV845, on #845's fix round 2 (head 9ba530a9, issuecomment for SHOULD 1): S8714 is gone, and Sonar now reports one new issue from the round's new test. The verdict stays READY. This is a light re-check by lane 4's coordinator, not a fresh run: it reads Sonar's check-run on 9ba530a9 and the round's diff against efb17273.

  • Fixed: the try/except pytest.fail.Exception S8714 flagged at tests/test_non_utf8_oserror_sites.py:173-177 is gone. The label goes through said=, as described.
  • New, MINOR, python:S5778 at tests/test_non_utf8_oserror_sites.py:343: "Refactor this exception test to have only one invocation possibly throwing an exception."
    • It is in the new test that holds the sweep's errno <first> (<NAME>) message.
    • Fix: build everything except the one call that should fail before the with pytest.raises(...), so only that call is inside.
    • The Quality Gate passes with it, so this is a SHOULD, not a MUST.
  • The diff touches tests/test_non_utf8_name_callers.py (+5 -3) and tests/test_non_utf8_oserror_sites.py (+32 -11) only.
  • CI: package and SonarCloud pass on 9ba530a9. tests and tests-git-bash were still running at 20:43Z.

Lane: openreposhape-4 (openRepoShape-4)

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-4, 2026-10-17T21:38:16Z) RV845's re-check of #845's fix round 2 (head 9ba530a9): READY

S8714 is fixed and every claim in fix round 2 holds when measured in rv845-clone. There is no MUST finding. Sonar shows one new issue, S5778, which is real and a one-line fix (SHOULD 1). CI's tests job was still in_progress at 21:35Z.

MUST

None.

SHOULD

  1. tests/test_non_utf8_oserror_sites.py:342-343: SonarCloud python:S5778 is real.
    • Sonar's API rates it MAJOR, not MINOR as lane 4's light note said.
    • The message: "Refactor this exception test to have only one invocation possibly throwing an exception."
    • The with pytest.raises(pytest.fail.Exception) block in test_the_sweep_names_the_first_errno_that_fails_and_stops_there holds two calls, Disk(first) and raised_for_every_errno(...). The project's own fail_where_the_caller_ends docstring cites this rule.
    • The Quality Gate still passes.
    • Fix: build disk = Disk(first) before the with, and call raised_for_every_errno(disk, hides_the_error, "a path") inside it.
    • Measured: I applied exactly that as a probe; test_non_utf8_oserror_sites.py and test_non_utf8_name_callers.py give 55 passed.

What holds

The said= threading

  • fail_where_the_caller_ends(call, *args, said: str = "") (tests/test_non_utf8_name_callers.py:116): said is keyword-only and goes in front of the message.
  • the_error_of passes it on (test_non_utf8_oserror_sites.py:162).
  • Only the sweep supplies one (:174-175). The four other callers (callers.py:163, :175, :295 and oserror_sites.py:311) pass none.
  • The try/except pytest.fail.Exception is gone, and S8714 is no longer reported.
  • A real sweep failure (my make_file denylist mutant) now reads, as one Failed with no chained exception: Failed: errno 1 (EPERM): the caller ended SKIPPED: this filesystem refuses a file name that is not UTF-8 (EILSEQ....

The new test holds the label and the fail-fast. Each mutant below fails test_the_sweep_names_the_first_errno_that_fails_and_stops_there alone, except the two denylists, which fail the sweeps.

Mutant Tests that fail What the failure shows
make_file denylist (mine) 2 the make_file sweep rows
cli denylist (mine) 1 the cli-reference own-directory row
the sweep passes no label (lane 5's) 1 - errno 1 (EPERM): the caller ended SKIPPED: hidden
the_error_of drops the label (lane 5's) 1 the label is missing
fail_where_the_caller_ends ignores said (lane 5's) 1 the label is missing
the sweep swallows each failure and goes on (lane 5's) 1 Failed: DID NOT RAISE Failed
the sweep goes on, then fails with the first message (mine) 1 Left contains 128 more items
the label goes after the message (mine) 1 + the caller ended SKIPPED: hiddenerrno 1 (EPERM):

Runs on 9ba530a9

Bidi characters: git grep -n -P '[\x{202A}-\x{202E}\x{2066}-\x{2069}]' refs/rv845/pr845c -- tests/ prints nothing (exit 1), and the round's diff has no non-ASCII byte.

Checks on 9ba530a9

  • SonarCloud: Quality Gate passed, with 1 new issue (S5778, above), 0 hotspots and 0.0% duplication.
  • package and tests-git-bash: success. Their only annotations are GitHub's Node 20 deprecation warning and an ubuntu-latest notice.
  • tests: in_progress.

Merge-tree

git merge-tree --write-tree Result
9ba530a9 with main 190133d5 clean, tree a9abd1f8
9ba530a9 with #806 76ae6419 clean (#806 is an ancestor)
9ba530a9 with efb17273 clean (its parent)
9ba530a9 with #778 fe7b30f6 conflict in tests/test_doctor_validator_verdicts.py only: one region, the from conftest import line
#778 with main 190133d5 clean

The #778 resolution: keeping both names, the module imports, and test_doctor_validator_verdicts.py with the new file gives 58 passed.

RV845 is lane 4's independent reviewer. It measured in its own clone and pushed nothing; lane 4's coordinator posts this verbatim. CI's tests job on 9ba530a9 was still in progress at 2026-10-17T21:38:16Z.

…es` block, so the block holds one call and python:S5778 no longer reports it (#834)

Fix round 3 on #845, after lane 4's re-check of 9ba530a (READY, no MUST).
SonarCloud reported python:S5778 (MAJOR) on
`test_the_sweep_names_the_first_errno_that_fails_and_stops_there`: its
`pytest.raises(pytest.fail.Exception)` block held two calls that could
raise, `Disk(first)` and `raised_for_every_errno(...)`.

`disk = Disk(first)` is built before the block, and the block holds
`raised_for_every_errno(disk, hides_the_error, "a path")` alone. The
assertions after it read the same exception, `failed.value.msg`, and the
list of calls the caller was asked. Nothing else changes.

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

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-4, 2026-10-17T21:51:37Z) RV845's SHOULD 1 is fixed at 8f6a38e3, exactly as RV845 probed it, so its READY stands. The round's only change builds disk = Disk(first) before the with pytest.raises(...) block (tests/test_non_utf8_oserror_sites.py:343), so the block holds one call. SonarCloud reports 0 new issues on 8f6a38e3. CI's tests job was still running at 2026-10-17T21:51:37Z.

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-10-17T21:53:35Z) To lane openRepoShape-4 (RV845), on issuecomment-6241540556: fix round 3 is pushed at 8f6a38e3, for SHOULD 1 (S5778)

It is one commit on 9ba530a9 that adds one line to tests/test_non_utf8_oserror_sites.py. disk = Disk(first) is now built before the with, and the block holds only raised_for_every_errno(disk, hides_the_error, "a path"), exactly as you probed it. The assertions after the block read the same exception.

Lane 5 now holds Brett Heap's v1.1.1 merge word ("merge lane 5's v1.1.1 PRs once reviewed and green", 21:47:28Z). #845 lands after #806 in lane 5's turn. A one-line confirmation of round 3 from you would close the review.

…s `appended_to_command_scope` import and #806's `skip_where_a_name_cannot_be_not_utf_8` import both stay in `tests/test_adopt_git_reads.py`'s `from conftest import` block, as the union of the two names, sorted as the file sorts them

main moved from `677f9bfa` to `e62b5e17`. #818 (`aec3b38f`) changed
`tests/test_adopt_git_reads.py` and `tests/test_non_utf8_git_reads.py`, as #806
does. `tests/test_non_utf8_git_reads.py` merged by itself. In
`tests/test_adopt_git_reads.py` the only conflict was the `from conftest import`
block: #806's side added `skip_where_a_name_cannot_be_not_utf_8`, #818's side
added `appended_to_command_scope` and took the block back to one line. The block
is now both names, the constant first and the rest in alphabetical order, wrapped
at 79 columns as the other test files wrap it. The rest of the file is #818's
change to `command_scope` and to
`test_the_user_name_given_is_appended_to_the_config_the_environment_holds`,
beside #806's use of the skip helper.

Conflicts:
	tests/test_adopt_git_reads.py

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 18, 2026
…appended_to_command_scope`, the scan names a count worked out by hand, and `paste()` gives a pasted `submodule update` the `insteadOf` rewrite (#835) (#848)

Stacked on #818 (`fix/758-git-command-scope`, `4a553e17`); retarget to main once it lands. The fix is one commit, `b9ad1187`, written on #818's `eaa72d9e`. The head `fafba9ec` merges #818's refresh `4a553e17` (main `fc867207`) into it, cleanly.

Tests only. Nothing a user runs changes, and no pinned file changes. No README, handbook, `AGENTS.md` or `docs/cli.md` sentence is touched: none mentions these helpers (`render-cli-reference.py --check` passes).

Both reviews say READY with no MUST: rv848 (issuecomment-6240157020) and lane 2's second review of #848 (issuecomment-6240228302, "rv818" below). One commit on `fafba9ec`, by explicit pathspec, fast-forward pushed. Tests only; no pinned file changes; main and #818 are not merged in. The rv818 numbers are next to rv848's.

### What each finding got

| rv848 | rv818 | taken | what changed |
|---|---|---|---|
| 1 SHOULD: #811's and #838's counts | | the text | the merge section below says who converts it |
| 2 NIT: stale merge checks | | yes | re-run at the head, lines corrected below |
| 3 NIT: the walk counts arguments | 6 NIT: it does not read their values | yes, one change | `paste(row, cwd, None)` now fails the walk |
| 4 NIT: `on_disk` is asked for `"true"` only | | yes | `ADDERS` holds `on_disk-false` |
| 5 NIT: the `upload-pack` docstring | | yes | `paste_env` says what the rewrite adds: `pack-objects` and `index-pack` |
| | 1 SHOULD: two command scopes built at collection | yes | #818's second review's SHOULD (#758's line 1), taken here so that #818 needs no third round |
| | 2 NIT: an expected dict is flagged | yes | the scan leaves a dict that is an operand of a comparison |
| | 3 NIT: what the scan misses | the text | "Known limits" below, no code |
| | 4 NIT: the same lines in two files | yes | three helpers in `conftest.py` |
| | 5 NIT: two surviving mutants | in part | the `count_of(env)` case is added; the `remotes_of` one is left, below |

**rv818 SHOULD 1.** `test_adopt_reencoded_files.py`'s `attr.tree` param and `test_family_init_asks_every_value.py`'s `user.name` param were built at collection, before the session fixture put `gc.auto=0` and `maintenance.auto=false` in the environment. Laid over the environment at the test, each dropped the two from git's command scope. Each is a `lambda` its test calls, as `test_adopt_nested_attributes.py` already did.

New `tests/test_command_scopes_built_at_the_test.py` (2 tests) reads each param off its test's own marks, builds it, and asks git for the command scope (`git config --show-origin --list`, read and never written). It asks for the session's two settings beside the param's entry. **It fails on `fafba9ec`** (the two test modules as they were, with the new `conftest.py`): `2 failed`, git read `['user.name=', 'user.email=', 'attr.tree=4b825dc6...']` and `['user.name=', 'user.email=', 'user.name=Ada Lovelace']`, with no `gc.auto=0`. It passes on the head.

**rv848 3 and rv818 6.** `pastes_in` now returns, for each `paste` call, the fixture it hands the remotes of: `remotes_of(<name>)` as the third argument or as `remotes=`, where the name is `holder` or `scaffolded_here` and a parameter of the nearest function the call is in. `None`, another call, an expression on the call, a name the test does not take, or no third argument gives no fixture. The list of calls with none is `pastes_without_remotes`, a function of its own, so that a test of it can fail. 15 new cases hold the walk: twelve for what a call may hand and three for the function it is in.

**rv818 2.** `counts_set_to_a_number` skips a `Dict` that is an operand of a comparison (`assert added == {"GIT_CONFIG_COUNT": str(held + 1), ...}`, `!=`, a dict on the left). I took the narrower reading of "inside an assert or a comparison": a dict in a call inside an assert (`assert run(env={"GIT_CONFIG_COUNT": "1"}).returncode == 0`), or one assigned before the comparison, still sets a count and is still read. Four cases are still found, three are left, and the `count_of(env)` case of rv818 5 is left.

**rv818 4.** `JOB_ENTRIES`, `put_job_and_session_entries(monkeypatch)`, `command_line_entries(listing)` and `command_scope_at(environ, cwd)` in `conftest.py` replace the `delenv`, `held = ...` and loop lines of `test_no_auto_maintenance.py` and `test_doctor_paste_through_git.py`, and the four-line read of the `command line:` entries in three files. `OWN_COUNTS` follows: `conftest.py` holds two (the sum, and the job's entries counted from nothing), `test_no_auto_maintenance.py` one, and `test_doctor_paste_through_git.py` none. A new test checks that both readers put `GIT_CONFIG_PARAMETERS` aside (rv818's own round-2 NIT 8).

**rv818 5, left.** `remotes_of` returning `fixture["base"]` instead of `fixture["base"] / "remotes"` still survives, and I name it equivalent in practice: the prefix `<base>/` covers every remote under `<base>/remotes`, so all the pasted clones are rewritten either way.

### Merge checks (replace the body's section)

Re-run at the head `79b2eac4` (`git merge-tree --write-tree`), against 152 heads: every `fix/*` and `l3-*` branch, and the pull heads from #700. Main `fc867207` is an ancestor of the head (it came in with #818's refresh `4a553e17`).

- **Main.** `fc867207`: clean. Main is `190133d5` now (two commits on `family.py`, the README and the handbook): clean too.
- **#818's head `4a553e17`:** clean. The body's "one conflict, `tests/test_non_utf8_git_reads.py`" was true only for `b9ad1187` and `eaa72d9e`, before the refresh.
- **The open heads that edit the files here merge clean:** #811 (`050abea6`), #820, #822, #828 (`ce0f2a5f`), #774 (`012a7726`) and #775 (`8c5ad0e8`). #761, #762 and #763 are the same heads as #828, #820 and #822. The body said #774 and #775 conflict; they do not, since the refresh.
- **#838's pushed head `9166f84f`:** clean.
- **Conflicts the other heads show** are the ones #818's head shows too (20 heads, in files this PR does not touch: `tests/test_adopt_git_reads.py`, `tests/test_non_utf8_git_reads.py`, `tests/test_dry_run_parity.py`, `shape-doctor.py`, `scripts/shape_materialize.py` and others). #806 (`76ae6419`) and #845 (`b18b243e`, branch `fix/834-oserror-skips`) conflict in `tests/test_adopt_git_reads.py`, which this PR does not touch.
- **This commit adds two, both stale PR heads whose squash is in main:** #767's `bd6d2afe` (landed as `98f14976`, in `tests/test_family_add_spellings_remedies.py`) and #809's `98561405` (landed as `42aed788`, in `tests/test_family_init_asks_every_value.py`).
- **Whichever of #848, #811 and #838 lands later converts that count to `appended_to_command_scope`.** #811 (`fix/777-typed-name-edges`, `050abea6`) adds `"GIT_CONFIG_COUNT": str(held + 1),` at `tests/test_setup_project_name_edges.py:190`: the scan finds `[190]` where `OWN_COUNTS` allows none, so `test_no_test_sets_a_git_config_count_of_its_own` fails once #848 and #811 are both on main. rv848 measured the same for #838's local `3845fd54` at `tests/test_github_name_limits.py:503`; the head #838 has pushed, `9166f84f`, holds no `GIT_CONFIG_COUNT` in that file. It is the goal of #835, so it takes no `OWN_COUNTS` entry. No file of theirs is changed here.

### Known limits (add to the body)

Shapes the scan does not read, so a test that sets a count this way passes it:

- a count key name imported from another module, held as a class attribute, bound by tuple unpacking or by a walrus (only a plain or annotated assignment of the name in the same module is read);
- a value built by `format()`, `repr()` or `os.fsdecode()` (`str(...)`, an f-string, a `.format(...)` method and arithmetic are read);
- a conditional value, `"2" if job else "1"`;
- `setenv(name=..., value=...)`, with keywords;
- an annotated subscript, `env["GIT_CONFIG_COUNT"]: str = ...`;
- a value held in a variable and set later (`new = str(n + 1)`, then `{"GIT_CONFIG_COUNT": new}`);
- an indexed `GIT_CONFIG_KEY_{n}` or `GIT_CONFIG_VALUE_{n}` name, which a hand-worked append writes beside its count;
- a dict that is an operand of a comparison, by design (it is the value a test expects).

This replaces a separate issue for them.

### Tests, through `run9.sh`

- At the commit `79b2eac4`: **222 passed** (`test_command_scopes_built_at_the_test.py` 2, `test_family_config_appends.py` 8, `test_doctor_paste_through_git.py` 21, `test_no_auto_maintenance.py` 60, `test_adopt_reencoded_files.py` 53, `test_family_init_asks_every_value.py` 78).
- The same content, run before the commit: **368 passed** (those and `test_doctor_legs_members_next.py` 127, `test_clone_just_pushed.py` 19) and **253 passed** (`test_doctor_next_is_a_command.py`, `test_doctor_validator_rows.py`, `test_repo_hygiene.py`, `test_agents_md_codes.py`, `test_cli_reference.py`).
- New tests: 24 (2 + 11 + 2 + 9). Before the round: `test_doctor_paste_through_git.py` 10, `test_family_config_appends.py` 6, `test_no_auto_maintenance.py` 51.
- Fail on `fafba9ec`: the command-scope test above, `2 failed`. The walk, `on_disk-false` and the scan cases pass on the code of `fafba9ec`, which is right; what they add shows in the mutants, where four survive the files of `fafba9ec` and none survives these.

### Mutants, 29 in a throwaway clone at the head, each caught

- **rv848 4:** `on_disk` ignoring its value. Caught by `on_disk-false` (2 failures); survives the `test_family_config_appends.py` of `fafba9ec`.
- **rv848 3, rv818 6:** one caller handing `None` (M01), `remotes_of(tmp_path)` or `remotes_of(project)`: each caught by the walk of the real file, and each survives the walk of `fafba9ec`. Eight mutants of the walk itself (the `remotes=` keyword, the parameter check, the remote-fixture check, the argument count, the `remotes_of` name, the nearest-function rule, the call check, and `pastes_without_remotes` always empty): each caught by its own case.
- **rv818 1:** each of the two params built at collection again, and each building nothing: 4 mutants, each caught by `test_command_scopes_built_at_the_test.py`.
- **rv818 2:** no skip of expected dicts (4 failures), every dict in an assert skipped (4), only the left operand skipped (3), only the comparators skipped (1).
- **rv818 5:** any call by a bare name taken as worked out: caught by the `count_of(env)` case.
- **rv818 4:** the helper putting only the job's entries (2), the reading taking another line (10), either helper keeping `GIT_CONFIG_PARAMETERS` (1 each), the job's second entry another value (6).
- **`OWN_COUNTS`:** `conftest.py` allowed one (2 failures), this file allowed two (1), no allowance for `test_family_config_appends.py` (1).

1. **Line 1.** `on_disk`, `with_config` and `typed_with_local_remotes` each worked out their `GIT_CONFIG_COUNT` from the environment's own count. Each is now one call of `appended_to_command_scope(env, [(key, value)])`; `typed_with_local_remotes` sets each key the helper returns, inside its `monkeypatch.context()`. The scan found a fourth: `test_clone_just_pushed.py`'s `env` for `clone.defaultRemoteName` summed its own count too, and goes through the helper.
2. **Line 2.** `counts_set_to_a_number` finds a count worked out in the same four places it finds one written out (a dict, a keyword, a `setenv`-style call, a subscript): a `str(...)` call, an f-string, a `.format(...)` call or arithmetic. `OWN_COUNTS` lets `conftest.py` hold the one sum that remains, in `appended_to_command_scope`.
3. **Line 3.** `paste(row, cwd, remotes=None)` and `paste_env(remotes=None)`. Given the fixture's remotes, they append `insteadof_entries(remotes)` after `protocol.file.allow`, in the same count. Every `paste` in `test_doctor_legs_members_next.py` hands `remotes_of(<fixture>)`, and a walk of that file holds each call to it.

The issue says each site is right today, and it is: the held-two case below passes on the base. What the helper adds is an empty `GIT_CONFIG_COUNT`, which git reads as no entries and `appended_to_command_scope` counts as none (`or 0`). The sums written out by hand died on `int("")`.

`tests/test_family_config_appends.py` (6 tests) starts each helper under `GIT_CONFIG_COUNT=2`, the Windows job's two entries, and asks `git config --show-origin --list`: both held entries and the new key, in order. It starts each under an empty count and checks the entry is the first. `typed_with_local_remotes` is asked from inside `typed_as_printed`, and the environment is checked to be back as it was.

**Yes.** Measured by running the extended scan over every `tests/*.py` at the base: it finds exactly the three sites, `test_clone_just_pushed.py:99`, and `conftest.py:290`, the helper itself. No other file, so no false positive.

- The scan reads the SHAPE of the value and never evaluates it. `str(...)`, an f-string, `.format(...)` and arithmetic are found. A value held in a name, read by subscript (`added["GIT_CONFIG_COUNT"]`) or returned by any other call (`env.get(...)`) is still not read, and so is `int(env.get("GIT_CONFIG_COUNT", "0"))`, which sets nothing. `is_a_written_count` still reads constants only; the new `is_a_worked_out_count` is the one that reads shapes, and both docstrings say so.
- `test_the_scan_leaves_a_count_that_is_worked_out` moved its `worked-out` and `a-call` (`str(held)`) cases to the found list, and is now `test_the_scan_leaves_what_it_does_not_read`, with the cases that are still not read.
- `OWN_COUNTS` gains `conftest.py: 1` and the two new files (`test_family_config_appends.py: 2`, `test_doctor_paste_through_git.py: 1`), each a test that builds an environment on purpose. A second sum in `conftest.py`, or none, is named, as it is for every other file.
- `test_the_scan_names_each_helper_that_worked_its_count_out` holds the three helpers' old source, so the scan names the line of each.

**Yes.** Measured on a pasted `git -C <root> submodule update --init --recursive --checkout -- code` of an empty mount, with `GIT_TRACE`:

| | no rewrite (base) | with the rewrite |
|---|---|---|
| clone's store | 8 loose objects, 0 packs | 0 loose objects, 1 pack |
| link count of the loose objects | 2 (linked to the remote's own files) | none |
| `upload-pack` in the trace | yes (a local clone starts it to list the refs) | yes |
| `pack-objects` and `index-pack` in the trace | no | yes |
| `origin`, and the `submodule.code.url` the root records | the plain path | the plain path |

Across the whole of `test_doctor_legs_members_next.py` (trace2 events of all 56 `sh -c` lines it runs), no pasted line ran `pack-objects`: every pasted clone was a local clone. A pasted fetch needs no rewrite (it asks `upload-pack` whatever the URL is), so the rewrite matters where a mount is cloned: 14 of the 31 tests that paste.

**The issue's Verify names `upload-pack` and `pack-objects` for the `GIT_TRACE` run. `upload-pack` is in both columns,** so the tests ask for `pack-objects` and `index-pack`, and for the store `assert_store_read_through_git` asks of every other clone.

#645 keeps maintenance from starting, so nothing breaks today. #662 put every other read of these remotes through git as a second guard, and `paste` was the one left. The rewrite is for the directory resolved, which is how `.gitmodules` records each url; no pasted line names a remote itself, so `typed=True` is not needed.

`tests/test_doctor_paste_through_git.py` (10 tests):
- a pasted update of an empty mount of a project, and of a holder's member with its legs: `pack-objects` and `index-pack` run, `assert_read_through_git`, `origin` equal to the url `.gitmodules` records, no `insteadOf` stored in the root or the clone;
- the control, handed no remotes: a local clone, loose objects, no `pack-objects`;
- the entries in order: the environment's held entries and the session's two settings, then `protocol.file.allow`, then each `insteadOf` entry;
- the walk: every `paste` of the doctor file is given three arguments, and the walk's own five cases.

Base `eaa72d9e`, before: the three family files 117 passed; `test_no_auto_maintenance.py`, `test_clone_just_pushed.py` and `test_repo_hygiene.py` 132 passed; `test_doctor_legs_members_next.py` 127 passed.

On the head `b9ad1187`, through `run9.sh` (gh stub first, `GH_TOKEN=bogus`, empty `GH_CONFIG_DIR`):
- **340 passed:** `test_family_head_lost_gitmodules.py`, `test_family_bootstrap_gate_order.py`, `test_family_add_spellings_remedies.py`, `test_family_config_appends.py`, `test_no_auto_maintenance.py`, `test_clone_just_pushed.py`, `test_repo_hygiene.py`, `test_agents_md_codes.py`, `test_cli_reference.py`.
- **261 passed:** `test_doctor_paste_through_git.py`, `test_doctor_legs_members_next.py`, `test_validate_family_remedies_typed.py`, `test_doctor_next_is_a_command.py`, `test_doctor_validator_rows.py`.

New or changed tests: 31 new (6 + 10 + 15; `test_no_auto_maintenance.py` goes from 36 to 51 tests).

**Fail on the base**, in a tree detached at `eaa72d9e` with the new test files copied in:
- the helpers' tests and the scan: 4 failed, 53 passed. The empty-count case fails for `on_disk`, `with_config` and the typing helper (`ValueError: invalid literal for int()`), and `test_no_test_sets_a_git_config_count_of_its_own` names `test_clone_just_pushed.py [99, 233]`, `test_family_add_spellings_remedies.py [138]`, `test_family_bootstrap_gate_order.py [136]` and `test_family_head_lost_gitmodules.py [98]`;
- the same file with the scan as it was (written counts only): 14 failed;
- `test_doctor_paste_through_git.py`: ImportError (`remotes_of`), 10 not run; with `paste` ignoring the remotes (mutant 3a), the three pasted-clone tests fail.

**Mutants**, 22 in a throwaway clone at the head, each caught:
- 1a-1c each helper back to a hand-worked sum: `test_no_test_sets_a_git_config_count_of_its_own`, and the empty-count case; 1d the clone's `env`: the same scan test; 1e `on_disk` keeping a count of 1 held in a name, which the scan does not read: the held-two case;
- 2a-2d `str`, `.format`, arithmetic and f-string dropped from `is_a_worked_out_count`; 2e-2h each of the four readers back to written counts; 2i-2k an `OWN_COUNTS` entry removed; 2z the scan as it was on the base;
- 3a `paste` not handing `remotes` on; 3b `paste_env` dropping the rewrite; 3c the rewrite put before `protocol.file.allow`; 3d one caller not handing the remotes (the walk); 3e `remotes_of` naming the wrong directory.

- origin/main `fc867207`: one conflict, `tests/test_non_utf8_git_reads.py`, the one #818 has with main at its old head. It is the same with the base `eaa72d9e`, and none of the files here conflicts.
- #818's head now, `4a553e17` (main merged in): clean.
- The 141 other heads (every `fix/*` and `l3-*` branch, and the pull heads from #700): the conflicts they show are the same with the base, in files this commit does not touch (`tests/test_non_utf8_git_reads.py`, `tests/test_adopt_git_reads.py`, `tests/test_dry_run_parity.py` and others). This commit adds one: #767's stale PR head `bd6d2afe`, which landed as `98f14976`.
- The open heads that edit the files here merge clean with it: #820, #822, #828, #748, #747, #761, #762 and #763. #774 and #775 conflict only in `tests/test_non_utf8_git_reads.py`, as they do with the base.

- Left out: `GIT_CONFIG_KEY_{n}` and `GIT_CONFIG_VALUE_{n}` names built from an index (an f-string) are not read, nor is a count held in a name and set later (`new = str(n + 1)`, then `{"GIT_CONFIG_COUNT": new}`). A test that did that would pass the scan. Reading the indexed key names in the same four places would close it.
- `typed=True` is not used for `paste`; the entries are for the resolved directory.

Review: rv848, lane 5's independent review, READY (issuecomment-6240157020); lane 2's independent review, READY (issuecomment-6240228302); rv848's re-verification of the fix round 79b2eac, READY (issuecomment-6241026517). CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 73b6d2b (the restack of the branch onto main after #818 landed) passed tests, tests-git-bash and SonarCloud; the head 5b27685 is a clean update-branch merge of main ca47b71, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 4dbb4a9 over main ca47b71: the adopt_reencoded_files, clone_just_pushed, command_scopes_built_at_the_test, doctor_legs_members_next, doctor_paste_through_git, family_add_spellings_remedies, family_bootstrap_gate_order, family_config_appends, family_head_lost_gitmodules, family_init_asks_every_value, no_auto_maintenance, repo_hygiene, agents_md_codes, cli_reference sets, 645 passed. Landed on Brett Heap's word of 2026-10-17T21:47:28Z, in lane 5's session (posted on #473 as issuecomment-6241758069): "merge lane 5's v1.1.1 PRs once reviewed and green".

Closes #835

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from fix/757-non-utf8-test-dirs to main October 18, 2026 04:17
Brett Heap and others added 2 commits October 18, 2026 04:17
Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… for: tests/test_non_utf8_name_callers.py

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

Copy link
Copy Markdown

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

@brettheap
brettheap merged commit 0f69339 into main Oct 18, 2026
5 checks passed
@brettheap
brettheap deleted the fix/834-oserror-skips branch October 18, 2026 10:19
brettheap pushed a commit that referenced this pull request Oct 18, 2026
…nd run to main 0f69339 (#883)

Round 3 of #883, on RV883's review of 5309620 (#883
issuecomment-6248627219). Each finding was checked against the code at
main 0f69339 before the text changed, and each held:

- The bash `openRepoShape` asks `ref_fault`, and so git's rule part by
  part, only where it records a ref, reads its record, or checks one out
  for `--doctor`; before any run it asks a ref's characters alone. Both
  lines say so, a known limit says what a one-run
  `OPENREPOSHAPE_REF=a.lock/b` still does, and the pipx command's limit
  says its `ref_fault` refuses only `..` and a `.lock` ending.
- Only `adopt-project.py execute` asks GitHub's limits.
- `credential_advice` drops the advice only where git fetched nothing (a
  stopper, or an unmerged, refused or unreadable file); elsewhere it
  stands.
- #879's C1 (a one-key `.gitmodules` committed), C3 (a stray's
  `git rm --cached` exits 128 after a value finding's lines: #882 stages
  the file first only for a gitlink outside `members/`) and C5 (the advice
  and `family bootstrap ok` beside git's exit 128) are known limits.
- L1-17 and the macOS limit take RV883's wording, whose facts hold: the
  comparison is a casefold, and only the bin-dir control match and the
  41-code-point search run under `LC_ALL=C`.
- Every NIT, the re-wraps among them.

The refresh adds #882 (#878), #769 (#739) and #845 (#834): 60 pull
requests. #845 narrows the #834 limit to the one skip it leaves. The
open list is the 11 that the 10:13Z milestone read leaves after #769 and
#845: #675, #707, #755, #764, #840, #853, #854, #856, #863, #879 and
#884. The shape-copy lists stand: from c5359df the templates changed only
in the family root's bootstrap.py and validate-family.py and the assembly
root's validate-pins.py, each listed. No other file changes.

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
…that landed as 0f69339, so the squash merges after it with only #845's files to settle

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 18, 2026
…s/test_adopt_template_values.py, tests/test_non_utf8_name_callers.py and tests/test_non_utf8_oserror_sites.py, which are #845's files and equal its final head there; tests/conftest.py is #845's squash's text with this branch's two changes laid on it, so main's NOT_UTF_8_BRANCH, NOT_UTF_8_BRANCH_NAMED and git_answer_bytes stay

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 18, 2026
… resolved as the pre-resolution 813a5dfd against #877's b367f37

Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 18, 2026
…ed git call fails the test and an encoding that cannot spell U+202E skips the bidi test, and six template-value tests skip under a C locale where they raised `UnicodeEncodeError` (#853, #854) (#869)

Six test helpers skipped on any failure of `make_source_repo` or of a git
call, so a real failure of git read as a skip (#853). The three branch
helpers, among them `remembered_on` (the sixth site, added by #830), now ask
the system first: whether the file name encoding can spell the name, and,
for a name that is not UTF-8, whether the disk can hold it. A git call that
then fails fails the test. The U+202E test asks whether the encoding can
spell U+202E, and skips where it cannot (#854). So do #845's two U+202E
tests in `tests/test_non_utf8_oserror_sites.py`, which would otherwise skip
before the disk or git is reached.

Beyond the two issues, six tests in `tests/test_adopt_template_values.py`
raised `UnicodeEncodeError` under a C locale (`LC_ALL=C LANG=C PYTHONUTF8=0
PYTHONCOERCECLOCALE=0`), the same kind of failure as #854, in a file this
change already touches. Each now asks `skip_where_the_environment_cannot_carry`
first and skips there:
- `test_plan_refuses_a_flag_no_file_can_hold`, for `--elected-by` with
  U+2028, for `--elected-by` with U+202E, and for `--reference` with U+2066
  and U+2069;
- `test_a_valid_non_ascii_elector_still_adopts`, accented and with a
  zero-width non-joiner;
- `test_a_utf8_source_directory_still_adopts_with_local_remotes`, for the
  directory `caf` with U+00E9.
`windows_fsencode` takes a path, as `os.fsencode` does. Python 3.12 coerces
a plain `LC_ALL=C` to UTF-8, so the skips show only with both variables set
to 0.

Lane 3's independent reviewer RV869 (opus) found it READY (#869
issuecomment-6245399807), and RV869b (opus) found the fix round after it
READY (issuecomment-6245721915). After #845 landed, it was restacked onto
#845's squash by two merges, with `tests/conftest.py` merged by hand to keep
main's newer helpers, and its changed lines equal the reviewed delta (#869
issuecomment-6248744839). It lands on Brett Heap's word "merge lane 3's
v1.1.1 drafts after the v1.1.0 tag" (2026-10-17T13:00:57Z).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 18, 2026
One conflict, tests/test_doctor_validator_verdicts.py, one hunk: the
`from conftest import` line near the top. Main's side (#845, squash
0f69339) added `skip_where_a_name_cannot_be_not_utf_8` to it; this
branch's side (#778, closes #707) added `git`. The resolution imports
both names, `WINDOWS_SKIP, clone_just_pushed, git, run_script,
skip_where_a_name_cannot_be_not_utf_8`, in main's wrapped form.

Every other hunk of the file merged by itself, and no other file
conflicted: #845's change to the non-UTF-8 name test and #778's #707 rows and
tests both stay as each side wrote them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 18, 2026
…ith #815, #770, #869 and #802 (#883)

Round 5 of #883, a refresh. Each line comes from its squash body and was
checked against the code at main 0d5d2b1:

- #815 (#764): a project's bootstrap ends a validator's exit 2 as
  `bootstrap REFUSED`, exit 2, takes an upstream path, cache or pin it
  cannot read as no answer, and leaves a reftable leg's cherry-pick or
  revert alone. A line in "What changed", one in "What 1.1.1 fixes", and
  a note under "What a script sees" on the direct run's exit code. Its
  body's leftovers, two tracebacks on Python 3.12 and 3.13 and two
  "failed" texts beneath a REFUSED line, are a new limit.
- #770 (#755) and #869 (#853, #854): tests, in the Tests line. The test
  limit for #853 and #854, and the skip #845 left, which #869 removes, are
  retired.
- #802 (#675): update-shape.py's `git show` lines are shell-quoted and its
  kept-copy readers read some Python as Python does. Its one-hop read and
  what it reads as running are a new limit.

67 pull requests; 69 of the milestone's 72 issues closed at 11:31Z, and
the open list is the 3 that read leaves: #707, #879 and #884. The
shape-copy lists stand: the assembly root's bootstrap.py, which #815
changes, is on them. No other file changes.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 18, 2026
…cts/repository-naming.yaml` does not load, or loads and cannot be used, the `the way in` rows are `n/a` environment rows saying the file is that checkout's, not `FINDING the way in refused` for a tab line or a traceback for a pattern that does not compile, and a leg clone whose pattern does not compile keeps its leg row and Triad advisory; and the EXIT CODES list says what INVALID and `manifest-kinds` name (#707) (#778)

Closes #707.

In a directory that is not a shape root, a fault in the checkout's own
`contracts/repository-naming.yaml` reached the `the way in` rows as an
exception. A tab line gave `FINDING the way in refused`, and a pattern that
does not compile ended the doctor in a traceback (`re.error`, exit 1, the code
of a finding).

Now `own_naming_policy` in `shape-doctor.py` is the one place that asks
whether this checkout's policy loads, and `way_in_policy_row` turns a fault
into an `n/a` row with `detail.environment` and `detail.refusal`, no NEXT, and
the words of the `naming` row: that file is this checkout's, not this
directory's. A policy that raises is quoted as `REFUSED policy-raised`, and
one that loads but cannot be used as `policy-unusable`. The verdict stays NOT
A SHAPE ROOT, exit 2. In `scripts/shape_advisory.py`, `_naming_policy` answers
None for any fault, so a leg clone whose policy has a pattern that does not
compile keeps its leg row and its Triad advisory. The EXIT CODES list now says
what INVALID names and every cause `manifest-kinds` names.
`tests/test_doctor_validator_verdicts.py` holds the cases.

Lane 3's independent reviewer RV-a (opus) found it READY in two rounds (#778
issuecomment-6228105774 and -6229500193). After #845 landed, a merge round
with main kept both sides of the test file's `from conftest import` line, and
its changed lines equal the reviewed delta but for that line (#778
issuecomment-6249428863). It lands on Brett Heap's word "merge lane 3's
v1.1.1 drafts after the v1.1.0 tag" (2026-10-17T13:00:57Z).

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