fix(tools): force UTF-8 output on fetch_page & robots_check (closes #685) (#686)

* fix(tools): force UTF-8 output on fetch_page & robots_check (closes #685)

On Windows, a piped stdout (used by CLI runners like Claude Code)
defaults to the host ANSI code page (cp1252 on Western installs).
When fetch_page.py --text outputs a webpage containing non-Latin
or accented characters outside cp1252 (e.g. Polish, Danish, Cyrillic,
or CJK), printing the tag-stripped text failed with UnicodeEncodeError.
Similarly, robots_check.py lacked explicit UTF-8 stream configuration.

Address this by forcing UTF-8 encoding on standard output and error:
- tools/fetch_page.py: call _force_utf8_output() on startup
- tools/robots_check.py: add _force_utf8_output() and call it in CLI
- tests/test_tools_utf8_output.py: add regression tests for both tools
- CHANGELOG.md: document fix under Unreleased

All 770 unit tests pass, along with lint_skills and security_guards.

* changelog: place the #686 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-19 21:46:48 +02:00
1 parent e5b98f698f
commit d621ebe635
4 files changed
+94

No files matched your search

+59
View File
@@ -171,6 +171,65 @@ class ToolsWriteUtf8(unittest.TestCase):
proc = run_legacy_stdout([repo / "tools" / "upstream_triage.py"], cwd=repo)
self.assert_clean_utf8(proc, 0, CYRILLIC)
def test_fetch_page_prints_non_latin_text(self):
import http.server
import threading
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self): # noqa: N802
if self.path == "/robots.txt":
self.send_response(404)
self.end_headers()
return
body = f"<html><body><h1>{POLISH}</h1><p>{CYRILLIC}</p></body></html>".encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args):
pass
server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
port = server.server_address[1]
out = self.tmp / "saved.html"
proc = run_legacy_stdout([
TOOLS / "fetch_page.py", f"http://127.0.0.1:{port}/posting", "--text", "--out", out,
])
self.assert_clean_utf8(proc, 0, POLISH, CYRILLIC)
finally:
server.shutdown()
server.server_close()
def test_robots_check_runs_cleanly(self):
import http.server
import threading
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self): # noqa: N802
self.send_response(404)
self.end_headers()
def log_message(self, *args):
pass
server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
port = server.server_address[1]
proc = run_legacy_stdout([
TOOLS / "robots_check.py", f"http://127.0.0.1:{port}/jobs/{POLISH}",
])
self.assert_clean_utf8(proc, 0, "ALLOWED")
finally:
server.shutdown()
server.server_close()
if __name__ == "__main__":
unittest.main()