mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
fix(web): handle non-UTF8 Windows console encodings in _build_web_ui
Codex review pointed out that even with the sync-assets fix applied,
_build_web_ui still crashes on a stock Windows console before reaching
npm: Python stdout defaults to cp1252 (or similar) and raises
UnicodeEncodeError when print() hits the arrow/check glyphs used for
status messages (→, ✗, ⚠, ✓). Reproduced locally in PowerShell:
$ PYTHONIOENCODING=cp1252 python -c "from hermes_cli.main import _build_web_ui; _build_web_ui(Path('web'), fatal=True)"
UnicodeEncodeError: 'charmap' codec can't encode character '\u2192' ...
The previous PR body claimed "end-to-end verified on Windows 11", but
that was under the venv's default (utf-8) stdout. A plain `py` or
PowerShell invocation would still fail before sync-assets ever ran.
Fix: inner _say() helper that falls back to
text.encode(sys.stdout.encoding, errors="replace")
when print() raises UnicodeEncodeError. Glyphs degrade to '?' on
ASCII / cp1252 consoles; utf-8 consoles are unaffected. Verified the
full build pipeline runs to completion with PYTHONIOENCODING=cp1252.
Scoped tightly to _build_web_ui (the function this PR already touches);
other call sites in the codebase with the same risk are out of scope.
This commit is contained in:
parent
0854640537
commit
38ea2a57a5
1 changed files with 23 additions and 11 deletions
|
|
@ -5681,13 +5681,25 @@ def _build_web_ui(web_dir: Path, *, fatal: bool = False) -> bool:
|
||||||
if not _web_ui_build_needed(web_dir):
|
if not _web_ui_build_needed(web_dir):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Console-encoding-safe print: Windows consoles default to cp1252
|
||||||
|
# (or similar) and will raise UnicodeEncodeError on arrow / check
|
||||||
|
# glyphs unless PYTHONIOENCODING=utf-8 is set. Routing every print
|
||||||
|
# in this function through _say() with errors="replace" keeps the
|
||||||
|
# build path usable on a stock `py -m hermes_cli.main web` invocation.
|
||||||
|
def _say(text: str) -> None:
|
||||||
|
try:
|
||||||
|
print(text)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
encoding = getattr(sys.stdout, "encoding", None) or "ascii"
|
||||||
|
print(text.encode(encoding, errors="replace").decode(encoding, errors="replace"))
|
||||||
|
|
||||||
npm = shutil.which("npm")
|
npm = shutil.which("npm")
|
||||||
if not npm:
|
if not npm:
|
||||||
if fatal:
|
if fatal:
|
||||||
print("Web UI frontend not built and npm is not available.")
|
_say("Web UI frontend not built and npm is not available.")
|
||||||
print("Install Node.js, then run: cd web && npm install && npm run build")
|
_say("Install Node.js, then run: cd web && npm install && npm run build")
|
||||||
return not fatal
|
return not fatal
|
||||||
print("→ Building web UI...")
|
_say("→ Building web UI...")
|
||||||
|
|
||||||
def _relay(result: "subprocess.CompletedProcess") -> None:
|
def _relay(result: "subprocess.CompletedProcess") -> None:
|
||||||
"""Print captured npm output so users can see *why* a step failed.
|
"""Print captured npm output so users can see *why* a step failed.
|
||||||
|
|
@ -5702,17 +5714,17 @@ def _build_web_ui(web_dir: Path, *, fatal: bool = False) -> bool:
|
||||||
continue
|
continue
|
||||||
text = blob.decode("utf-8", errors="replace").rstrip() if isinstance(blob, bytes) else blob.rstrip()
|
text = blob.decode("utf-8", errors="replace").rstrip() if isinstance(blob, bytes) else blob.rstrip()
|
||||||
if text:
|
if text:
|
||||||
print(text)
|
_say(text)
|
||||||
|
|
||||||
r1 = _run_npm_install_deterministic(npm, web_dir, extra_args=("--silent",))
|
r1 = _run_npm_install_deterministic(npm, web_dir, extra_args=("--silent",))
|
||||||
if r1.returncode != 0:
|
if r1.returncode != 0:
|
||||||
print(
|
_say(
|
||||||
f" {'✗' if fatal else '⚠'} Web UI npm install failed"
|
f" {'✗' if fatal else '⚠'} Web UI npm install failed"
|
||||||
+ ("" if fatal else " (hermes web will not be available)")
|
+ ("" if fatal else " (hermes web will not be available)")
|
||||||
)
|
)
|
||||||
_relay(r1)
|
_relay(r1)
|
||||||
if fatal:
|
if fatal:
|
||||||
print(" Run manually: cd web && npm install && npm run build")
|
_say(" Run manually: cd web && npm install && npm run build")
|
||||||
return False
|
return False
|
||||||
# First attempt
|
# First attempt
|
||||||
r2 = subprocess.run(
|
r2 = subprocess.run(
|
||||||
|
|
@ -5747,20 +5759,20 @@ def _build_web_ui(web_dir: Path, *, fatal: bool = False) -> bool:
|
||||||
# A stale UI is far better than no UI for non-interactive callers
|
# A stale UI is far better than no UI for non-interactive callers
|
||||||
# (Windows Scheduled Tasks, CI) — issue #23817.
|
# (Windows Scheduled Tasks, CI) — issue #23817.
|
||||||
if dist_index.exists():
|
if dist_index.exists():
|
||||||
print(" ⚠ Web UI build failed — serving stale dist as fallback")
|
_say(" ⚠ Web UI build failed — serving stale dist as fallback")
|
||||||
if stderr_tail:
|
if stderr_tail:
|
||||||
print(f" Build error:\n {stderr_tail}")
|
_say(f" Build error:\n {stderr_tail}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
print(
|
_say(
|
||||||
f" {'✗' if fatal else '⚠'} Web UI build failed"
|
f" {'✗' if fatal else '⚠'} Web UI build failed"
|
||||||
+ ("" if fatal else " (hermes web will not be available)")
|
+ ("" if fatal else " (hermes web will not be available)")
|
||||||
)
|
)
|
||||||
_relay(r2)
|
_relay(r2)
|
||||||
if fatal:
|
if fatal:
|
||||||
print(" Run manually: cd web && npm install && npm run build")
|
_say(" Run manually: cd web && npm install && npm run build")
|
||||||
return False
|
return False
|
||||||
print(" ✓ Web UI built")
|
_say(" ✓ Web UI built")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue