Skip to content

fix(doctor): decode subprocess output as UTF-8 on non-UTF-8 (GBK) locales - #49510

Closed
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/doctor-subprocess-utf8-gbk-49499
Closed

fix(doctor): decode subprocess output as UTF-8 on non-UTF-8 (GBK) locales#49510
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/doctor-subprocess-utf8-gbk-49499

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

This is a sibling follow-up to #18637 (GBK locale crash, bug 3)

What does this PR do?

hermes doctor crashes on Chinese Simplified Windows (CP936/GBK) with UnicodeDecodeError: 'gbk' codec can't decode byte 0xaa raised inside subprocess._readerthread. The text-mode subprocess.run(..., text=True) calls in doctor decode child output using the system locale; on a GBK locale a child emitting UTF-8 bytes triggers the crash, taking down the first command users reach for to diagnose their environment. This pins UTF-8 decoding with errors="replace" on every text-mode subprocess site in doctor.

Related Issue

Fixes #49499

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • hermes_cli/doctor.py: added encoding="utf-8", errors="replace" to the SSH connectivity probe and the npm audit --json subprocess calls (the two text=True sites — grep text=True hermes_cli/doctor.py confirms these are the only two; the docker-info and gh-auth-status calls are bytes-mode and never decode). Covers all text-mode subprocess decode sites in doctor.
  • tests/hermes_cli/test_doctor.py: regression test simulating a GBK locale (UnicodeDecodeError unless UTF-8 is pinned), proving doctor's subprocess path survives — fails before / passes after.

How to Test

uv run --with pytest --with pytest-asyncio python3 -m pytest tests/hermes_cli/test_doctor.py -v

Checklist

Code

  • My code follows the project style
  • I added a regression test that fails before and passes after
  • Tested on macOS (GBK locale simulated in-test; no Windows hardware)

Documentation & Housekeeping

  • No documentation changes needed
  • No AI-attribution added

…ales

hermes doctor crashes on Chinese Simplified Windows (CP936/GBK) with
UnicodeDecodeError: 'gbk' codec can't decode byte 0xaa raised inside
subprocess._readerthread. The text-mode subprocess.run(..., text=True)
calls in doctor decode child output using the system locale; on a GBK
locale a child emitting UTF-8 bytes triggers the crash, taking down the
first command users reach for to diagnose their environment.

Pin encoding="utf-8", errors="replace" on the two text-mode subprocess
sites in doctor (the SSH connectivity probe and the npm audit --json
call). The docker-info and gh-auth-status calls are bytes-mode and never
decode, so they are unaffected. This is a sibling follow-up to NousResearch#18637,
which fixed the .env file-read GBK path but did not touch the subprocess
decode path.

Fixes NousResearch#49499
Copilot AI review requested due to automatic review settings June 20, 2026 07:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes hermes doctor crashing on GBK/CP936 Windows locales by forcing UTF-8 decoding (with replacement) for subprocess output in doctor’s text-mode subprocess.run probes.

Changes:

  • Pin encoding="utf-8", errors="replace" on doctor’s SSH connectivity probe subprocess call.
  • Pin encoding="utf-8", errors="replace" on doctor’s npm audit --json subprocess call.
  • Add a regression test that simulates a GBK-locale UnicodeDecodeError at the subprocess text-decoding boundary.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
hermes_cli/doctor.py Forces UTF-8 decoding for the two text=True subprocess call sites to avoid GBK-locale decode crashes.
tests/hermes_cli/test_doctor.py Adds a regression test simulating GBK subprocess decode failures to ensure doctor survives and exits normally.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +135 to +143
# Drive the SSH connectivity probe (the first text-mode subprocess
# site in run_doctor) by selecting the ssh terminal backend.
monkeypatch.setenv("TERMINAL_ENV", "ssh")
monkeypatch.setenv("TERMINAL_SSH_HOST", "example.invalid")

hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
monkeypatch.setattr(doctor_mod, "HERMES_HOME", hermes_home)

@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists labels Jun 20, 2026
… Copilot review)

The existing TestDoctorSubprocessEncoding test drives only the SSH probe
text-mode subprocess site; it never reaches the second text-mode site, the
`npm audit --json` call, because _safe_which("npm") is not stubbed and
PROJECT_ROOT/node_modules does not exist. Stripping encoding="utf-8",
errors="replace" from the npm-audit subprocess.run would therefore not fail
any test.

Add a sibling test that stubs _safe_which for node/npm, monkeypatches
PROJECT_ROOT to a tmp dir with a node_modules/ subdir so run_doctor()
actually executes the npm-audit subprocess under the same GBK decode
simulation. Because that call is wrapped in a broad `except Exception: pass`,
a UnicodeDecodeError there is swallowed rather than crashing, so the test
asserts on the observable outcome — the "deps (no known vulnerabilities)"
result line — which is present only when the audit decodes successfully.
Verified it fails if the npm-audit encoding/errors kwargs are removed.
@briandevans

Copy link
Copy Markdown
Contributor Author

@copilot Addressed in 38b67a3 (test-only). Added TestDoctorSubprocessEncoding.test_doctor_npm_audit_decodes_utf8_on_gbk_locale, which stubs _safe_which for node/npm and monkeypatches PROJECT_ROOT to a tmp dir with a node_modules/ subdir so run_doctor() actually executes the npm audit --json subprocess under the same GBK decode simulation as the SSH test.

One nuance worth flagging: unlike the SSH probe, the npm-audit call is wrapped in a broad except Exception: pass, so a GBK UnicodeDecodeError there is swallowed rather than crashing — asserting on SystemExit alone could not distinguish the fixed and unfixed code. The new test instead asserts on the observable outcome (the <label> deps (no known vulnerabilities) result line), which only appears when the audit decodes successfully. Verified fail-before/pass-after: removing encoding="utf-8", errors="replace" from the npm-audit subprocess.run makes the new test fail while the SSH test still passes.

@briandevans

Copy link
Copy Markdown
Contributor Author

Draining our contribution queue to keep it lean and reviewable \u2014 closing this as stale (no reviewer traction in 2+ weeks). If the underlying issue is still live on current main, we'll re-file a fresh, focused fix.

@briandevans briandevans closed this Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hermes doctor crashes with UnicodeDecodeError on Chinese Windows GBK locale

3 participants