Skip to content

fix(gateway): decode schtasks output defensively on Windows - #36033

Closed
konsisumer wants to merge 1 commit into
NousResearch:mainfrom
konsisumer:fix/windows-gateway-schtasks-decode
Closed

fix(gateway): decode schtasks output defensively on Windows#36033
konsisumer wants to merge 1 commit into
NousResearch:mainfrom
konsisumer:fix/windows-gateway-schtasks-decode

Conversation

@konsisumer

@konsisumer konsisumer commented May 31, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

This fixes the third defect reported in #34083: on native Windows, hermes gateway status (and the stop/restart paths) printed an alarming UnicodeDecodeError traceback from a background subprocess reader thread, even though the command itself succeeded:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xce in position 2: invalid continuation byte
Exception in thread Thread-7 (_readerthread): ...

The noise comes from _exec_schtasks() in hermes_cli/gateway_windows.py. It ran schtasks.exe with subprocess.run(..., text=True) but no explicit encoding/errors. schtasks emits text in the console's localized code page (e.g. cp936/GBK on Chinese Windows). When the ambient locale resolves to UTF-8 — common under git-bash/MSYS, the reporter's setup — Python's reader thread decodes those bytes with strict UTF-8 and raises, spamming a traceback on an otherwise-successful command.

The fix pins the decode to encoding="utf-8", errors="replace", the same defensive pattern already used by find_gateway_pids() in hermes_cli/gateway.py. The ASCII keys the status parser reads (status, last run result, last run time) are unaffected; only localized free-text degrades to U+FFFD instead of crashing.

Scope note (the other two bugs in #34083)

#34083 bundles three Windows defects. This PR intentionally addresses only the decode bug, so it uses Refs rather than a closing trailer:

  • Bug 1 — plugin discovery skips plugins/web/ddgs: already resolved on main. plugins/web/ddgs/plugin.yaml now ships (since the ddgs migration), and the bundled-plugin scanner already recurses one level into category dirs like web/ (_scan_directory_level), discovering it as key web/ddgs. The reporter was on a site-packages release that predates this. No code change needed.
  • Bug 2 — gateway restart exits with 0xC000013A (STATUS_CONTROL_C_EXIT): appears to be a console CTRL_C_EVENT reaching the CLI process during schtasks /End / /Run. The likely fix is a transient SetConsoleCtrlHandler/SIG_IGN guard around the restart (mirroring the gateway-run absorber). That is a Windows-only signal-handling change I cannot exercise or verify on this (macOS) worker, so I am deferring it to a separate, Windows-verified PR rather than ship an unverified signal change. This remaining work is why Windows: three bugs from daily use (plugin discovery, gateway exit code, Unicode decode #34083 stays open.

Related Issue

Refs #34083

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • hermes_cli/gateway_windows.py: _exec_schtasks() now passes encoding="utf-8", errors="replace" to subprocess.run, so localized schtasks console output can never raise UnicodeDecodeError in the reader thread.
  • tests/hermes_cli/test_gateway_windows.py: added test_exec_schtasks_decodes_defensively, asserting the schtasks subprocess call is made with the defensive decode kwargs.

How to Test

  1. Run the focused test: pytest tests/hermes_cli/test_gateway_windows.py -q (30 passed locally).
  2. Run the surrounding gateway/status slice for regressions: pytest tests/hermes_cli/test_gateway_windows.py tests/hermes_cli/test_gateway.py tests/gateway/test_status.py tests/gateway/test_status_command.py -q (131 passed locally).
  3. Repro context (native Windows, Chinese locale, git-bash): before the fix, hermes gateway status printed a UnicodeDecodeError / Exception in thread Thread-7 (_readerthread) traceback while still reporting the gateway state; after the fix the command output is clean.

What platforms tested on

  • macOS (darwin-arm64), local: full test slice above passes. The new test mocks the Windows code path (_assert_windows no-op + patched subprocess.run), so it runs and verifies the fix on any platform.
  • Native Windows 10 / Python 3.11 (the reporter's environment): the decode bug is Windows-only and I could not run the live schtasks path on this worker. The fix is a contained, behavior-preserving decode change matching an existing in-tree pattern; no POSIX code path is touched.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(gateway):)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix (no unrelated commits)
  • I've run the relevant pytest slice and all tests pass (targeted gateway/status tests, 131 passed)
  • I've added tests for my changes
  • I've tested on my platform: macOS 15 / darwin-arm64 (Windows path covered via mocks)

Documentation & Housekeeping

  • I've updated relevant documentation — N/A (internal subprocess fix)
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) — scripts/check-windows-footguns.py clean; POSIX paths untouched

hermes gateway status/restart spawned a UnicodeDecodeError traceback in
subprocess's reader thread when schtasks emitted localized (cp936/GBK)
console bytes under a UTF-8 ambient locale (git-bash/MSYS). Pin the
decode to encoding=utf-8, errors=replace so the command stays quiet,
matching the pattern already used by find_gateway_pids().

Refs NousResearch#34083
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard labels May 31, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #24478 — same encoding='utf-8', errors='replace' approach for schtasks.exe UnicodeDecodeError on localized Windows. Also competing with #24417 (mbcs approach) and #34646 (ctypes GetOEMCP, triaged as most correct).

@Henry-916

Copy link
Copy Markdown

+1 — Reproduced and verified on Chinese Windows 10 (Python 3.11, git-bash).

One additional finding: the _FALLBACK_PATTERNS regex in gateway_windows.py only matches English/Spanish/Czech "access denied" messages. On zh-CN Windows, schtasks outputs "错误: 拒绝访问。" which doesn't match, so the Startup folder fallback path is never reached.

I patched both _exec_schtasks (encoding fix) AND _FALLBACK_PATTERNS/_ACCESS_DENIED_PATTERN (added Chinese patterns like 拒绝访问|无法打开|找不到|指定的帐户) to fully resolve the issue. Without the fallback pattern fix, hermes gateway install raises a RuntimeError instead of gracefully falling back to the Startup folder.

Also worth noting: the missing gateway-service/*.cmd script was the root cause of "no process detected after 6s" — the Scheduled Task was registered but pointed to a non-existent script file. Recreating it via _write_task_script() fixed the immediate startup failure.

@konsisumer

Copy link
Copy Markdown
Contributor Author

Closing — it looks like #38334 (merged) addresses the same change. If that's incorrect, feel free to reopen and ping me.

@konsisumer konsisumer closed this Jun 3, 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 P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants