fix(ssh): tolerate non-UTF-8 subprocess output (errors="replace", encoding="utf-8") - #37451
fix(ssh): tolerate non-UTF-8 subprocess output (errors="replace", encoding="utf-8")#37451Chrisyk wants to merge 5 commits into
Conversation
|
Thanks for flagging the overlap. For context: I reported #37130 yesterday and explicitly noted I was working on the fix, so I opened this PR to follow through. Beyond claiming it first, I'd suggest this PR as the consolidation target because it's the most complete of the three:
Also worth noting: #37265 and #37142 fix the ssh.py sites, but neither addresses the latent _popen_bash case in base.py (the comment there claims errors="replace" but the code never set it). So the same crash class survives on the drain path in both. Happy to reconcile with #37265 / #37142 and to fold in anything they cover that I don't, or have them review here. Whatever's least work for maintainers. |
combatsheep
left a comment
There was a problem hiding this comment.
Strong regression coverage here. Process-wise, the duplicate-PR consolidation concern raised below still looks worth resolving before merge so maintainers only have one place to review the fix.
|
Thanks for the focused regression coverage. The strict-decoding failure is still present on current The proposed Current main has added Automated hermes-sweeper review. |
|
Closing as resolved by PR #70875 (merged, commit 0f732cb), which closed out this bug class codebase-wide: every |
What does this PR do?
The SSH backend crashed with
UnicodeDecodeErrorduring_ensure_remote_dirs()(and other init steps) when internal SSH subprocesses emitted non-UTF-8 bytes, which is common on Windows OpenSSH/Git Bash targets. The code usedsubprocess.run(..., text=True)with strict UTF-8 decoding, so a single bad byte bricked all terminal/file/search tools before any user command ran.This PR makes all text-mode subprocess decoding in the SSH and base execution backends tolerant and deterministic by passing
errors="replace"andencoding="utf-8", so that malformed bytes are replaced with U+FFFD instead of raising an exception.Related Issue
I originally filed and claimed issue #37130, so I'm opening this PR since I was already working on the fix.
Fixes #37130
Type of Change
Changes Made
tools/environments/ssh.py: addederrors="replace",encoding="utf-8"to all 7 text-modesubprocess.run(...)calls_establish_connection,_detect_remote_home,_ensure_remote_dirs,_scp_upload(mkdir + scp),_ssh_bulk_upload(parent mkdir), and_ssh_delete. (the encoding="utf-8" pin is adapted from fix: decode SSH subprocess output tolerantly #37265 / honor2030)tools/environments/base.py: addederrors="replace",encoding="utf-8"to the_popen_bashsubprocess.Popen(..., text=True)call. A nearby comment already claimed theTextIOWrapperwas built witherrors="replace",encoding="utf-8", but the code never set it, leaving the fallback drain path with the same latent crash; the code now matches the documented intent.tests/tools/test_ssh_environment.py: addedTestNonUTF8SubprocessOutput(5 regression tests). A faithfulsubprocess.runstand-in that decodes raw bytes exactly as real Python does, reproducing the originalUnicodeDecodeErroron unfixed code. Covers full__init__, non-UTF-8 on stderr,_detect_remote_homereplacement-char output,_ensure_remote_dirs, and a contract test asserting every text-moderuncall (including the file-sync sites__init__doesn't reach) passeserrors="replace",encoding="utf-8".tests/tools/test_base_environment.py: addedTestPopenBashDecoding(1 regression test) that spawns a real subprocess emitting\xff\xfeand asserts the output reads as U+FFFD rather than crashing.How to Test
pytest tests/tools/test_ssh_environment.py tests/tools/test_base_environment.py -q→ all pass (live-SSH integration tests skip withoutTERMINAL_SSH_HOST).errors="replace",encoding="utf-8"arguments fromtools/environments/ssh.pyandtools/environments/base.py, re-run the two suites, and observe the 6 newtests fail with
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff. Restore the fix, and they pass again._ensure_remote_dirs().Checklist
Code
fix(scope):,feat(scope):,etc.)
Documentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/Aguide
Specifically targets Windows OpenSSH/Git Bash byte output
Screenshots / Logs
Regression tests failing on unfixed code (fix temporarily removed):
Same suites with the fix in place (11 skipped -- Live-SSH tests):
Live-SSH Integration Test (10 Success, 1 Failure):
Live SSH integration tests: 10/11 passed against Windows OpenSSH/Git Bash. The only failure is TestOneShotSSH.test_state_does_not_persist, which also exists unchanged on main and is unrelated to this PR’s non-UTF8 decoding fix; it indicates pre-existing one-shot SSH shell state persistence.