Skip to content

fix(gateway): pass encoding='utf-8' to all read_text/write_text in update flow (fixes #37423) - #37647

Closed
Morad37 wants to merge 1 commit into
NousResearch:mainfrom
Morad37:fix/37423-gateway-windows-utf8-file-io
Closed

Morad37 wants to merge 1 commit into
NousResearch:mainfrom
Morad37:fix/37423-gateway-windows-utf8-file-io

Conversation

@Morad37

@Morad37 Morad37 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes hermes update-over-gateway on Windows with a non-UTF-8 system locale. The 20 bare Path.read_text() / Path.write_text() calls in gateway/run.py (no encoding=) were using Python's locale codec instead of UTF-8, which crashes the gateway command handler with UnicodeEncodeError (when the user's gateway reply contains emoji/CJK) or UnicodeDecodeError / silent mojibake (when reading the hermes update subprocess's captured UTF-8 stdout). Both subclass ValueError, not OSError, so the surrounding except OSError guards don't catch them.

Changes

  • gateway/run.py — adds encoding="utf-8" to all 14 .read_text() and 6 .write_text() call sites in the update flow (lines 2113, 2140, 3813, 3843, 3889, 7354, 7374, 10690, 14770, 14910, 14944, 14982, 14992, 15017, 15035, 15128, 15142, 15148, 15082, 15201). Handles both single-line and multi-line write_text(...) shapes.
  • tests/gateway/test_windows_utf8_file_io.py — new file with 5 tests:
    • AST-based static guard: every .read_text() and .write_text() call site in gateway/run.py must pass encoding= (catches the original bug, plus the inverse regression of someone using encoding=locale.getpreferredencoding()).
    • Defence-in-depth: at least 20 call sites expected (catches whole-flow deletions).
    • Behavioural round-trip test that writes and reads an emoji/CJK payload under a fake cp1252 locale — proves the fix actually works.

How to test

  1. ~/.hermes/hermes-agent/venv/bin/python -m pytest tests/gateway/test_windows_utf8_file_io.py -q — 5/5 pass.
  2. ~/.hermes/hermes-agent/venv/bin/python -m pytest tests/gateway/ -q --timeout=60 — all gateway tests pass, no regressions.
  3. Confirmed the new tests correctly fail when one of the new encoding="utf-8" kwargs is reverted (manually walked the file, ran tests, restored).

Notes

  • Both pre-existing static guardrails (ruff PLW1514 and scripts/check-windows-footguns.py) explicitly only flag builtin open(), not Path.read_text() on a variable receiver. The new test fills that gap for the gateway module specifically.
  • One call at line 2140 was a multi-line write_text (the file path + JSON payload span 3 lines); handled by passing the encoding="utf-8" on its own line in the closing tuple, matching the rest of the codebase's style.
  • No behaviour change on POSIX — UTF-8 is the default there, and the explicit encoding="utf-8" kwarg is just noise on Linux. The change matters on Windows.

Closes #37423

…date flow (fixes NousResearch#37423)

gateway/run.py had 20 bare Path.read_text() / Path.write_text() calls
with no encoding=. On Windows with a non-UTF-8 system locale
(cp1252 on US installs, GBK/CP936 on Chinese installs), Python uses
the locale codec instead of UTF-8, which crashes the gateway command
handler with UnicodeEncodeError (when the user's gateway reply
contains emoji/CJK) or UnicodeDecodeError/mojibake (when reading the
hermes update subprocess's captured UTF-8 stdout).

Both errors subclass ValueError, not OSError, so the surrounding
except OSError guards don't catch them and the gateway command
handler / update-stream coroutine dies.

Both static guardrails (ruff PLW1514, scripts/check-windows-footguns.py)
explicitly only flag builtin open() and don't see Path.read_text() on
a variable receiver. The new tests/gateway/test_windows_utf8_file_io.py
adds a focused AST-based regression guard that walks the gateway AST
and asserts every .read_text() and .write_text() call site passes an
explicit encoding= kwarg, plus a behavioural round-trip test that
writes and reads an emoji/CJK payload under a fake cp1252 locale.

Closes NousResearch#37423
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery labels Jun 2, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing a real Windows encoding failure: current gateway/run.py:9040 still writes an update response without an encoding, and hermes_bootstrap.py:32-37 confirms bootstrap cannot change current-process file defaults.

Problems

  • The update IPC fix is incomplete: hermes_cli/main.py:4600/:4608 and platform response writers such as plugins/platforms/telegram/adapter.py:5668, plugins/platforms/feishu/adapter.py:2155, plugins/platforms/discord/adapter.py:7143, and gateway/platforms/qqbot/adapter.py:1204 use the same .update_* protocol with bare text I/O.
  • tests/gateway/test_windows_utf8_file_io.py:116 fixes a source call-site count, which is a change-detector test rather than a behavior contract (AGENTS.md:80-87, AGENTS.md:1309-1355).
  • tests/gateway/test_windows_utf8_file_io.py:171-194 opens a separate cp1252 file but does not alter Path defaults or invoke gateway code; its tested calls already explicitly use UTF-8.

Suggested changes

  • Cover all production update-IPC readers/writers, then replace the static count and standalone round trip with targeted temp-HERMES_HOME update-flow coverage for non-ASCII payloads.

Automated hermes-sweeper review.

f"{missing[:5]}"
)

def test_no_bare_read_text_or_write_text_remains(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixed minimum call-site count is a change-detector: moving or consolidating update I/O can fail the test without changing the UTF-8 contract. Please remove it and test the update IPC behavior instead.


# Force Python to use cp1252-style behaviour for bare open()
# so we catch the regression even if a future refactor drops
# the encoding= kwarg.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not simulate a cp1252 default: opening a separate file with _io.open(..., encoding="cp1252") never changes Path.write_text() defaults, and this call explicitly passes UTF-8. Exercise the gateway/CLI update IPC path with non-ASCII data instead.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users area/install-update Installer, updater, packaging, wheels, doctor labels Jul 13, 2026
@teknium1

Copy link
Copy Markdown
Collaborator

Closing as resolved by PR #71078 (merged, commit d372fda), which retired the bare read_text/write_text class codebase-wide: 139 sites guarded (11 contributor PRs salvaged with authorship preserved — earliest submitters credited — plus an AST sweep), with a CI linter rule and AST guard test preventing regressions. Every site this PR touches is guarded on current main — verified per-site before closing. Thanks for the fix; the number of independent PRs on this class is what escalated it to the campaign.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/install-update Installer, updater, packaging, wheels, doctor comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Windows gateway hermes update flow crashes with UnicodeEncodeError/UnicodeDecodeError (bare read_text/write_text under cp1252/GBK)

3 participants