Skip to content

bump-leg.py (a real bump) and update-shape.py apply run the project's own validators as children that cannot print its display name on a cp1252 stdout, so a project named 日本 is refused and rolled back as bump-leg-validators-red or update-validators-red under PYTHONIOENCODING=cp1252:surrogateescape, and the refusal blames the validators #254

Closed

Description

@brettheap

Lane: openreposhape-5 (openRepoShape-5)

Found by this lane's adversarial review of #214's fix (PR #246, fix/plan-report-stdout-encoding) as S1, and scoped out of that PR as a separate change. It composes with lane 3's PR #230 (fix/decode-git-output-utf8), which rewrites the same bump-leg.py run_validator call (see Expected). Pre-existing on main 54aa69a: there the tools raise earlier, at their own prints, which #246 fixes; the validators below fail the same way on main, behind that.

What is wrong. bump-leg.py and update-shape.py apply run the PROJECT's own validators as child processes after they rewrite the pin: bump-leg.py's run_validator (scripts/bump-leg.py:386) captures the child's output, and update-shape.py's (update-shape.py:1424) lets the child inherit stdout. The children are shape copies (scripts/validate-pins.py, scripts/validate-manifest.py), and they do not call #246's escape_what_stdout_cannot_encode. A child takes its stdout CONFIGURATION from the environment (PYTHONIOENCODING, the locale) but not the parent's reconfigure. So on a stdout whose code page cannot hold the project's display name, validate-manifest.py raises UnicodeEncodeError at print(f"manifest ok: {manifest.get('name')} "... (templates/assembly-root/scripts/validate-manifest.py:707), exits 1, and the bump or apply is refused and rolled back for a reason that has nothing to do with the pins. The reach is any project whose display name holds a character the console's code page lacks: a redirected stdout on a Western-European Windows is cp1252 with surrogateescape (CPython's Windows default), which holds no CJK name; a Japanese Windows uses cp932, which holds 日本, and other ANSI code pages differ. I cannot run Windows; the run below emulates it with PYTHONIOENCODING=cp1252:surrogateescape.

SHAPE=<checkout of fix/plan-report-stdout-encoding at 2d60c3e (PR #246)>
SHAPE_BUMP=<the same checkout with fix/decode-git-output-utf8 at fbdf8db (PR #230) merged in, uncommitted>
TMP=$(mktemp -d); cd "$TMP"
ENC=cp1252:surrogateescape        # a redirected stdout on a Western-European Windows
GIT='git -c user.name=T -c user.email=t@example.invalid'
# the shape at A (a clone of $SHAPE), a project scaffolded from it, and the shape's commit B: one template changes
git clone -q "$SHAPE" shape && ( cd shape && git checkout -q --detach HEAD )
python3 shape/scaffold-project.py --org testorg --project Atlas --elected-by 'T H' --elected-on 2026-09-02 \
    --local-remote-dir remotes --work-dir work >scaffold.out 2>&1 || exit 1
git -c protocol.file.allow=always clone -q --recurse-submodules remotes/Atlas.git base || exit 1
git clone -q remotes/Atlas-spec.git adv && ( cd adv && printf '# new\n' > NEW.md && git add NEW.md \
    && $GIT commit -qm advance && git push -q origin HEAD:refs/heads/adv ) && NEW=$(git -C adv rev-parse HEAD)
( cd shape && printf '\n# an upstream fix\n' >> templates/assembly-root/scripts/validate-manifest.py \
    && $GIT commit -qm B -- templates/assembly-root/scripts/validate-manifest.py ) && B=$(git -C shape rev-parse HEAD)
# the project is renamed to a display name cp1252 cannot encode (a Japanese Windows' cp932 can, others cannot)
rename() { sed -i 's/^name: "Atlas"$/name: "日本"/' project.yaml && $GIT commit -qam rename && git checkout -q -b work; }
run() {   # run <label> <command...>: on a fresh copy of the renamed project
  label=$1; shift; rm -rf "p-$label"; cp -a base "p-$label"; ( cd "p-$label" && rename )
  ( cd "p-$label" && "$@" ) >"o-$label" 2>"e-$label"; echo "== $label: exit $?"
  grep -a -E '^UnicodeEncodeError|^REFUSED [a-z-]*' "o-$label" "e-$label" | sed 's/^[^:]*://' | cut -c1-90 | sort -u
}
export PYTHONIOENCODING=$ENC
run validator python3 scripts/validate-manifest.py
run bump python3 "$SHAPE_BUMP/scripts/bump-leg.py" --root . --leg spec --to "$NEW" --local-remote-dir "$TMP/remotes"
run update python3 "$SHAPE/update-shape.py" apply --root . --yes --upstream "$TMP/shape" --at "$B"
unset PYTHONIOENCODING
rm -rf p-control && cp -a base p-control && ( cd p-control && git checkout -q -b work \
    && PYTHONIOENCODING=$ENC python3 "$SHAPE/update-shape.py" apply --root . --yes --upstream "$TMP/shape" --at "$B" >/dev/null 2>&1; echo "== control (no rename), update-shape apply: exit $?" )

Observed (the script cuts each line at 90 characters; each REFUSED line goes on, in the tool's own output, with "Every byte this command wrote has been rolled back"):

== validator: exit 1
UnicodeEncodeError: 'charmap' codec can't encode characters in position 13-14: character m
== bump: exit 2
REFUSED bump-leg-validators-red: scripts/validate-manifest.py exited 1 after the rewrite. 
UnicodeEncodeError: 'charmap' codec can't encode characters in position 13-14: character m
== update: exit 2
REFUSED update-validators-red: after the rewrite, scripts/validate-manifest.py refused. Ev
UnicodeEncodeError: 'charmap' codec can't encode characters in position 13-14: character m
== control (no rename), update-shape apply: exit 0

The control, with the display name Atlas, applies and exits 0, so the refusal is the name. Both tools roll back correctly, so no tree is damaged: a project named 日本 simply cannot be bumped or re-synced under such a stdout, and the refusal blames the validators. PYTHONIOENCODING=utf-8:strict takes the same commands to exit 0. On Linux the … in the pin validator's output is read by a UTF-8 locale as U+FFFD, an artefact of the emulation: on a Western-European Windows the locale codec is cp1252 and reads it back. Without #230 merged, the bump run dies earlier on the parent's decode of that output (UnicodeDecodeError, #230's subject), not on this validator.

Expected. A real bump and an apply on a project named 日本 succeed under a cp1252 stdout. The fix belongs in the two tools, not in the shipped validators (shape copies, digest-pinned in each project's contracts/shape-pin.yaml); shape-doctor.py:823 already sets PYTHONIOENCODING=utf-8 for the same validators. So give the child an explicit PYTHONIOENCODING:

It composes with #230, which gives bump-leg.py's run_validator errors="replace" and no encoding=, "so that codec is the one that reads it back": the two changes choose one decoding for that call. The #214 writer's trial git merge-tree of the utf-8 variant against #230's head conflicted in scripts/bump-leg.py (not re-run here), so this lands after #230 and chooses one decoding for that call: errors="replace" with an explicit encoding="utf-8", once the child is told to write UTF-8. Tests: a real (not --dry-run) bump against local bare remotes on a project renamed 日本, and update-shape.py apply the same way, each under PYTHONIOENCODING=cp1252:surrogateescape, as the script above runs them.

Reach. Anyone whose project display name holds a character outside the Windows code page of a stdout that is not the console (a file, tee, CI, an agent: the audience of AGENTS.md), running a real bump-leg.py or update-shape.py apply. A --dry-run bump runs no validator and is unaffected. shape-doctor.py is not affected, since it sets the child's encoding. Nothing in this repository writes such a name: scaffold-project.py writes name: from --project and refuses a --project outside [A-Za-z0-9._/@+~-] (REFUSED unsafe-value, checked on main with --project 日本), so the name reaches the manifest by a hand edit.

Found by. handoffs/openRepoShape/attachments/lane-openRepoShape-5/review214-report.md (S1) and rereview214-report.md (S1 scoped out; R3 corrected two sentences of the first text) in opensoft/brett-wip. The three runs above are new for this issue, on #246's head 2d60c3e and a throwaway merge of #230's head fbdf8db.

Not claimed.

🤖 Generated with Claude Code

Activity

  1. brettheap commented on Oct 11, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-5, session e62c2fd7-176d-429d-89fb-0653dd08ad5f@Eagle, 2026-10-11T22:26:55Z, for #254

    Logged in lanes/log/openRepoShape-5.md at fe2cb84cce60d6f767954631822269a4762f3f8d.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#254:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#254"`, kept where the row or its body names `#254`:
    246	adopt-project.py and five other tools escape a name their stdout cannot encode, so `plan` on `日本.md` under a cp1252 stdout (strict, or Windows' default surrogateescape) no longer exits 1 after writing the plan (#214)	fix/plan-report-stdout-encoding	MERGED
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `254` as a whole token:
       none
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions