Force UTF-8 on clicker output so Windows stops failing runs
A run died with "'charmap' codec can't encode character '▶'" at step 1. Python picks the console code page for stdout, and under PM2 - where stdout is a pipe rather than a console - that is cp1252 on Windows. cp1252 handles the em dashes in these files but not the run markers, so the first one raised UnicodeEncodeError from inside run_steps and the runner reported it as a step failure rather than an output problem. Reconfigure stdout and stderr to UTF-8 at the top of each entry point, with errors="replace" as a backstop for streams that cannot be reconfigured. Fixing the encoding once beats stripping the glyphs from ~30 call sites, and covers manual runs in a plain console too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
724ba5581c
commit
fbe2c47ed4
@@ -31,6 +31,22 @@ import urllib.request
|
|||||||
import actions
|
import actions
|
||||||
import focus
|
import focus
|
||||||
|
|
||||||
|
|
||||||
|
def force_utf8_output() -> None:
|
||||||
|
"""Make stdout/stderr accept the status glyphs this tool prints.
|
||||||
|
|
||||||
|
Windows picks the console code page for stdout, and under PM2 (where stdout
|
||||||
|
is a pipe, not a console) that is cp1252. It can encode em dashes but not
|
||||||
|
the run markers, so the first one raised UnicodeEncodeError mid-run and the
|
||||||
|
runner reported it as a step failure. errors="replace" is a backstop for any
|
||||||
|
stream that cannot be reconfigured at all.
|
||||||
|
"""
|
||||||
|
for stream in (sys.stdout, sys.stderr):
|
||||||
|
try:
|
||||||
|
stream.reconfigure(encoding="utf-8", errors="replace")
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
DEFAULT_API = "http://localhost:3000"
|
DEFAULT_API = "http://localhost:3000"
|
||||||
POLL_INTERVAL = 0.25
|
POLL_INTERVAL = 0.25
|
||||||
|
|
||||||
@@ -176,6 +192,7 @@ def describe(found: dict, factor: float) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
force_utf8_output()
|
||||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
parser.add_argument("action", choices=["locate", "click", "type"],
|
parser.add_argument("action", choices=["locate", "click", "type"],
|
||||||
help="locate = measure only; click = measure then click; "
|
help="locate = measure only; click = measure then click; "
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ Nothing is clicked. The cursor is moved and put back where it started.
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from clicker import force_utf8_output
|
||||||
|
|
||||||
|
|
||||||
def line(label: str, value: str) -> None:
|
def line(label: str, value: str) -> None:
|
||||||
print(f" {label:<22} {value}")
|
print(f" {label:<22} {value}")
|
||||||
@@ -125,6 +127,7 @@ def check_foreground() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
force_utf8_output()
|
||||||
print("AutoFirmer clicker diagnostics")
|
print("AutoFirmer clicker diagnostics")
|
||||||
check_platform()
|
check_platform()
|
||||||
check_dpi()
|
check_dpi()
|
||||||
|
|||||||
+2
-1
@@ -25,7 +25,7 @@ import time
|
|||||||
|
|
||||||
import actions
|
import actions
|
||||||
import focus
|
import focus
|
||||||
from clicker import Dashboard, DashboardError, NotFoundError
|
from clicker import Dashboard, DashboardError, NotFoundError, force_utf8_output
|
||||||
|
|
||||||
POLL_SECONDS = 1.0
|
POLL_SECONDS = 1.0
|
||||||
HEARTBEAT_SECONDS = 2.0
|
HEARTBEAT_SECONDS = 2.0
|
||||||
@@ -362,6 +362,7 @@ def finish(dash, run_id, error):
|
|||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
force_utf8_output()
|
||||||
parser = argparse.ArgumentParser(description=__doc__,
|
parser = argparse.ArgumentParser(description=__doc__,
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
parser.add_argument("--api", default="http://localhost:3000", help="dashboard URL")
|
parser.add_argument("--api", default="http://localhost:3000", help="dashboard URL")
|
||||||
|
|||||||
Reference in New Issue
Block a user