Skip to content

Commit f01cac6

Browse files
jawwad-aliclaude
andauthored
fix(scripts): stop setup-tasks text mode crashing on a legacy code page (#3892)
_check_file/_check_dir hard-code U+2713/U+2717 and print() them to sys.stdout. On Windows sys.stdout falls back to the ANSI code page whenever stdout is not a console — which is every time an agent or a workflow step captures the output — and U+2713 is unencodable in cp1252, so the document listing aborted mid-report with UnicodeEncodeError. This is the byte-identical twin of the block in scripts/python/check_prerequisites.py, which I flagged in the PR for that file rather than widening its scope. Fall back to ASCII when stdout cannot encode the glyph. "[OK]"/"[FAIL]" is the rendering these markers already have in-tree: Test-FileExists in scripts/powershell/common.ps1 emits exactly those, and normalize_status_text in tests/parity_helpers.py maps the glyphs onto them. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 6fa8c9a commit f01cac6

2 files changed

Lines changed: 46 additions & 4 deletions

File tree

scripts/python/setup_tasks.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,31 @@ def _available_docs(paths: FeaturePaths) -> list[str]:
5555
return docs
5656

5757

58+
def _status_marker(ok: bool) -> str:
59+
"""Return the status glyph, downgraded to ASCII when stdout cannot encode it.
60+
61+
On Windows sys.stdout falls back to the ANSI code page whenever it is not a
62+
console - a pipe or a file redirect, which is how agents and workflow steps
63+
invoke these scripts - and U+2713 is unencodable in cp1252, so printing it
64+
raised UnicodeEncodeError and aborted the report mid-listing.
65+
"[OK]"/"[FAIL]" is the ASCII rendering these markers already have in-tree:
66+
see Test-FileExists in scripts/powershell/common.ps1 and
67+
normalize_status_text in tests/parity_helpers.py.
68+
"""
69+
glyph = "✓" if ok else "✗"
70+
try:
71+
glyph.encode(getattr(sys.stdout, "encoding", None) or "utf-8")
72+
except (LookupError, UnicodeEncodeError):
73+
return "[OK]" if ok else "[FAIL]"
74+
return glyph
75+
76+
5877
def _check_file(path: Path, description: str) -> None:
59-
marker = "✓" if path.is_file() else "✗"
60-
print(f" {marker} {description}")
78+
print(f" {_status_marker(path.is_file())} {description}")
6179

6280

6381
def _check_dir(path: Path, description: str) -> None:
64-
marker = "✓" if _dir_has_entries(path) else "✗"
65-
print(f" {marker} {description}")
82+
print(f" {_status_marker(_dir_has_entries(path))} {description}")
6683

6784

6885
def main(argv: list[str] | None = None) -> int:

tests/test_setup_tasks_python_parity.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,28 @@ def test_missing_template_error_matches_all_variants(repo: Path) -> None:
205205
assert bash.returncode == ps.returncode == py.returncode == 1
206206
assert bash.stdout == ps.stdout == py.stdout == ""
207207
assert bash.stderr == ps.stderr == py.stderr
208+
209+
210+
def test_python_text_output_survives_a_legacy_stdout_code_page(repo: Path) -> None:
211+
"""Text mode must not crash when stdout cannot encode the status glyphs.
212+
213+
On Windows sys.stdout falls back to the ANSI code page whenever it is not a
214+
console — which is every time an agent or a workflow step captures the
215+
output. U+2713 is unencodable in cp1252, so printing it raised
216+
UnicodeEncodeError and truncated the document listing. The ASCII fallback is
217+
the rendering these markers already have in-tree (Test-FileExists in
218+
scripts/powershell/common.ps1, and normalize_status_text).
219+
"""
220+
feature = repo / "specs" / "001-my-feature"
221+
(feature / "research.md").write_text("# research\n", encoding="utf-8")
222+
(feature / "contracts").mkdir()
223+
224+
env = clean_env()
225+
env["PYTHONIOENCODING"] = "cp1252"
226+
result = run(py_cmd(repo, SCRIPT), repo, env=env)
227+
228+
assert result.returncode == 0, result.stderr
229+
assert "UnicodeEncodeError" not in result.stderr
230+
for doc in ("research.md", "data-model.md", "contracts/", "quickstart.md"):
231+
assert doc in result.stdout, (doc, result.stdout)
232+
assert "[OK] research.md" in normalize_status_text(result.stdout), result.stdout

0 commit comments

Comments
 (0)