Skip to content

fix(scripts): pin stderr to UTF-8 in the config scripts - #2795

Open
aranellaeth wants to merge 2 commits into
bmad-code-org:mainfrom
aranellaeth:fix/pin-stderr-utf8-config-scripts
Open

fix(scripts): pin stderr to UTF-8 in the config scripts#2795
aranellaeth wants to merge 2 commits into
bmad-code-org:mainfrom
aranellaeth:fix/pin-stderr-utf8-config-scripts

Conversation

@aranellaeth

Copy link
Copy Markdown
Contributor

Problem

The three scripts under src/scripts write diagnostics that quote user-controlled text:

  • resolve_config.py:67 and resolve_customization.py:84f"error: {error}\n", where the message carries the offending config path and tomllib's parse error.
  • memlog.py:148 — quotes path; memlog.py:154 — quotes the --field key=value pair straight from argv.

Only stdout is pinned (write_json_stdout). stderr is left at the platform default, so on a Windows console (cp1252) the filename the user needs in order to fix the problem comes back as escapes.

Measured, same run with and without the pin:

with:     error: failed to parse ...\proje-şık\_bmad\custom\bmad-fake.toml: ...
without:  error: failed to parse ...\proje-şık\_bmad\custom\bmad-fake.toml: ...

Change

Generalizes the pin_utf8 helper already merged for brain.py in #2578 to the three scripts, and calls it on stderr at main() entry. write_json_stdout now goes through the same helper instead of its own inline reconfigure.

errors= is passed through deliberately: reconfigure(encoding=...) alone resets the handler to "strict", which would silently downgrade stderr's POSIX backslashreplace default and turn a diagnostic about an undecodable path into a traceback.

Test

Adds one subprocess test that drives resolve_customization.py with PYTHONIOENCODING=cp1252 against a project root whose path contains Turkish characters, and asserts the path appears readable in stderr with no traceback.

Verified it discriminates: with the pin_utf8(sys.stderr) line stripped the test fails on exactly that assertion; restored, it passes.

src/scripts/tests42 passed (41 before, +1 new).

Diff is +102 / -7 across three scripts and one test file.

The three scripts under src/scripts write diagnostics that quote user-controlled
text: the config path, tomllib's parse message, and memlog's --field pair. Only
stdout was pinned, so on a Windows console (cp1252) that text comes back as
escapes while the user is trying to read which file to go fix.

Generalizes the pin_utf8 helper merged for brain.py in bmad-code-org#2578 to resolve_config,
resolve_customization and memlog. errors= is passed through so stderr's POSIX
backslashreplace default is not silently downgraded to strict.

Adds a subprocess test driving resolve_customization under PYTHONIOENCODING=cp1252
against a project path containing Turkish characters. It fails without the stderr
pin and passes with it.
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 505b8dcf-29f5-4c85-ac40-0fd7e44f4d38

📥 Commits

Reviewing files that changed from the base of the PR and between c16dc72 and 89baad4.

📒 Files selected for processing (4)
  • src/scripts/memlog.py
  • src/scripts/resolve_config.py
  • src/scripts/resolve_customization.py
  • src/scripts/tests/test_resolve_customization.py

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The scripts now pin console streams to UTF-8 while preserving existing error handlers. A regression test verifies that malformed configuration diagnostics with non-ASCII paths remain readable under cp1252 console settings.

Changes

Console encoding handling

Layer / File(s) Summary
Stream pinning and script integration
src/scripts/memlog.py, src/scripts/resolve_config.py, src/scripts/resolve_customization.py
Each script adds or uses a helper that sets UTF-8 encoding and preserves the stream error handler. stdout and stderr are pinned before output or diagnostics.
Non-ASCII diagnostic regression test
src/scripts/tests/test_resolve_customization.py
The test runs with cp1252 console encoding and verifies that a malformed configuration reports a non-ASCII path without UnicodeEncodeError or a traceback.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 89baa

The scripts now emit readable UTF-8 diagnostics while preserving existing error handling, improving troubleshooting for paths containing non-ASCII characters. No actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: bmadcode

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: pinning stderr to UTF-8 in the configuration scripts.
Description check ✅ Passed The description accurately explains the encoding problem, implementation, error-handler behavior, affected scripts, and test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Pins the configuration scripts’ console streams to UTF-8 while preserving each stream’s existing error handler.

  • Reuses a common pin_utf8 helper for JSON output and stderr diagnostics.
  • Applies UTF-8 stream configuration at each script’s entry point.
  • Adds subprocess coverage for a non-ASCII path under a simulated cp1252 console.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/scripts/memlog.py Adds UTF-8 pinning for stdout and stderr before dispatching memlog subcommands.
src/scripts/resolve_config.py Refactors stdout encoding through pin_utf8 and pins diagnostic output to UTF-8.
src/scripts/resolve_customization.py Applies the shared UTF-8 stream behavior to resolved customization output and errors.
src/scripts/tests/test_resolve_customization.py Verifies that parse errors preserve readable non-ASCII paths when the inherited I/O encoding is cp1252.

Reviews (2): Last reviewed commit: "docs(scripts): restore and extend docstr..." | Re-trigger Greptile

… touches

The first commit folded write_json_stdout's body into pin_utf8 and dropped its
existing docstring in resolve_config.py along with it. That was unintended;
restored, and its sibling in resolve_customization.py now has the same.

Also documents main() in the three scripts and the two test entities this PR
touches, taking docstring coverage over the touched set from 4/13 to 11/13.
@aranellaeth

Copy link
Copy Markdown
Contributor Author

Pushed ff78f08 to address the docstring coverage check, and it turned out to be covering a real slip rather than a style gap.

The first commit folded write_json_stdout's body into the new pin_utf8 helper and dropped its existing docstring in resolve_config.py along with it:

-def write_json_stdout(output) -> None:
-    """Pin stdout to UTF-8 — a Windows cp1252 default cannot encode emoji icons."""

That was unintended. It is restored, and its sibling in resolve_customization.py now carries the same. The follow-up also documents main() in the three scripts and the two test entities this PR touches.

Docstring coverage over the touched set goes from 4/13 to 11/13 (84.6%). The follow-up is +8/-0, pure additions. src/scripts/tests still passes 42.

The two test methods that remain undocumented predate this PR and I did not touch their bodies, so I left them alone rather than widening the diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant