fix(windows): platform._syscmd_ver stub in bootstrap + PYTHONUTF8 in desktop backend env

Two gaps found auditing the decode-crash cluster:

1. suppress_platform_ver_console() only ran in hermes_cli.main processes;
   slash workers, tui_gateway/entry, run_agent, batch_runner, and cli.py
   import only hermes_bootstrap and were exposed to both the console
   flash and (on Python 3.11.0/3.11.1, which lack CPython's
   encoding='locale' fix) a UnicodeDecodeError inside platform.win32_ver()
   under PEP 540 — the crash #69413 reported. Move the stub into
   hermes_bootstrap so every entry point gets it; the _subprocess_compat
   copy stays for non-bootstrap callers.

2. The desktop Electron spawn built the backend env without PYTHONUTF8,
   so anything the Python child emitted before hermes_bootstrap ran
   (interpreter startup errors, pre-bootstrap tracebacks) decoded with
   the locale default. Re-port of PR #56499's env half (echoriver89) to
   backend-env.ts (original targeted the deleted backend-env.cjs);
   explicit user setting wins.
This commit is contained in:
teknium1 2026-07-24 13:59:48 -07:00 committed by Teknium
parent 8516324f84
commit 0fb0ba475d
4 changed files with 111 additions and 0 deletions

View file

@ -68,6 +68,24 @@ test('buildDesktopBackendEnv extends PYTHONPATH and backend PATH together', () =
assert.ok(env.PATH.includes('/opt/homebrew/bin'))
})
test('buildDesktopBackendEnv forces PYTHONUTF8 unless the user set it explicitly', () => {
const defaulted = buildDesktopBackendEnv({
hermesHome: '/Users/test/.hermes',
currentEnv: { PATH: '/usr/bin' },
platform: 'darwin',
pathModule: path.posix
})
assert.equal(defaulted.PYTHONUTF8, '1')
const optedOut = buildDesktopBackendEnv({
hermesHome: '/Users/test/.hermes',
currentEnv: { PATH: '/usr/bin', PYTHONUTF8: '0' },
platform: 'darwin',
pathModule: path.posix
})
assert.equal(optedOut.PYTHONUTF8, '0')
})
test('normalizeHermesHomeRoot maps profile homes back to the global Hermes root', () => {
assert.equal(
normalizeHermesHomeRoot('/Users/test/.hermes/profiles/oracle', { pathModule: path.posix }),

View file

@ -104,6 +104,13 @@ function buildDesktopBackendEnv({
return {
PYTHONPATH: appendUniquePathEntries([...pythonPathEntries, currentPythonPath], { delimiter }),
// Force PEP 540 UTF-8 mode in the spawned Python backend so its stdio and
// subprocess defaults are UTF-8 even on non-UTF-8 Windows locales (GBK,
// cp1252, ...). hermes_bootstrap sets this inside the child too, but only
// after import — anything emitted earlier (interpreter startup errors,
// pre-bootstrap tracebacks) still decodes with the locale default without
// this. User's explicit setting wins. Re-port of PR #56499 (echoriver89).
PYTHONUTF8: currentEnv?.PYTHONUTF8 ?? '1',
[key]: buildDesktopBackendPath({
hermesHome,
venvRoot,

View file

@ -122,6 +122,49 @@ def apply_windows_utf8_bootstrap() -> bool:
return True
def suppress_platform_ver_console() -> None:
"""Stub ``platform._syscmd_ver`` on Windows — decode-crash + flash guard.
CPython's ``platform.win32_ver()`` (reached via ``platform.uname()`` /
``platform.platform()``, which the OpenAI SDK touches for its
platform headers) shells out ``cmd /c ver``. Two failure modes:
- **Console flash**: the ``check_output(..., shell=True)`` call has no
``CREATE_NO_WINDOW``, so a windowless parent (pythonw gateway, slash
workers, kanban workers) flashes a visible console per call.
- **UnicodeDecodeError on Python 3.11.0/3.11.1**: those micros lack
CPython's ``encoding="locale"`` fix (added 3.11.2), so under PEP 540
UTF-8 mode (which we enable above) the ``ver`` output OEM code page
bytes on localized Windows is strict-utf-8 decoded and raises,
crashing ``platform.platform()`` in any process that inherits
``PYTHONUTF8=1`` (issue #69413).
Stubbing ``_syscmd_ver`` to return its inputs makes ``win32_ver()`` hit
its documented fallback and read the version from
``sys.getwindowsversion()`` same data, in-process, no subprocess.
Mirrors ``hermes_cli._subprocess_compat.suppress_platform_ver_console``
(kept there for callers that don't import bootstrap); double
application is harmless. Lives here so EVERY entry point gets it
``tui_gateway/slash_worker.py``, ``tui_gateway/entry.py``,
``run_agent.py``, ``batch_runner.py``, and ``cli.py`` import only
``hermes_bootstrap``, never ``hermes_cli.main``.
"""
if not _IS_WINDOWS:
return
try:
import platform
if hasattr(platform, "_syscmd_ver"):
def _quiet_syscmd_ver(system="", release="", version="",
supported_platforms=("win32", "win16", "dos")):
return system, release, version
platform._syscmd_ver = _quiet_syscmd_ver
except Exception:
# Hardening only — never let it break an entry point.
pass
def harden_import_path(src_root: str | None = None) -> None:
"""Stop a package in the current directory from shadowing Hermes modules.
@ -188,6 +231,7 @@ def activate_durable_lazy_target() -> None:
# the very top of their module, before importing anything else. The
# import side effect does the right thing.
apply_windows_utf8_bootstrap()
suppress_platform_ver_console()
# Activate the durable lazy-install target (immutable Docker images) so
# packages installed into the data volume on a previous run are importable

View file

@ -396,3 +396,45 @@ class TestHardenImportPath:
sys.path[:] = original
if original_env is not None:
os.environ["HERMES_PYTHON_SRC_ROOT"] = original_env
class TestSuppressPlatformVerConsole:
"""suppress_platform_ver_console: stub applied on Windows, no-op on POSIX."""
def test_noop_on_posix(self, monkeypatch):
import platform
hb = _fresh_import()
original = getattr(platform, "_syscmd_ver", None)
monkeypatch.setattr(hb, "_IS_WINDOWS", False)
hb.suppress_platform_ver_console()
assert getattr(platform, "_syscmd_ver", None) is original
def test_stub_applied_when_windows(self, monkeypatch):
import platform
hb = _fresh_import()
original = getattr(platform, "_syscmd_ver", None)
try:
monkeypatch.setattr(hb, "_IS_WINDOWS", True)
hb.suppress_platform_ver_console()
stubbed = platform._syscmd_ver
assert stubbed is not original
# Stub returns its inputs — win32_ver()'s documented fallback path.
assert stubbed("s", "r", "v") == ("s", "r", "v")
# No-arg call (how Lib/platform.py invokes it in the fallback
# probe) must not raise — the rejected PR #69522 wrapper
# TypeError'd here.
assert stubbed() == ("", "", "")
finally:
if original is not None:
platform._syscmd_ver = original
def test_never_raises(self, monkeypatch):
hb = _fresh_import()
monkeypatch.setattr(hb, "_IS_WINDOWS", True)
import platform
original = platform._syscmd_ver
try:
monkeypatch.delattr(platform, "_syscmd_ver")
hb.suppress_platform_ver_console() # hasattr guard → silent no-op
finally:
platform._syscmd_ver = original