Skip to content

Fix Windows deadlock: decode extension subprocess pipes as UTF-8 - #282

Open
OPTI-james wants to merge 3 commits into
lightningpixel:devfrom
OPTI-james:fix/win-utf8-extension-pipes
Open

Fix Windows deadlock: decode extension subprocess pipes as UTF-8#282
OPTI-james wants to merge 3 commits into
lightningpixel:devfrom
OPTI-james:fix/win-utf8-extension-pipes

Conversation

@OPTI-james

Copy link
Copy Markdown

Problem

On Windows, extension generations wedge silently mid-run (stuck at ~80%, idle GPU/CPU), and HuggingFace weight downloads stall the same way. Nothing is reported — the run just never finishes.

Root cause

ExtensionProcess launches the worker with bare text=True, so the host decodes the worker's stdout/stderr with the locale codec — cp1252 on Windows. The moment the worker prints something cp1252 can't decode (tqdm's block glyphs, e.g. = e2 96 8f; byte 0x8f is undefined in cp1252), the stderr reader thread dies:

Exception in thread Thread-12 (_stderr_loop):
  File "...\api\services\extension_process.py", line 213, in _stderr_loop
    ch = stream.read(1)
  File "...\encodings\cp1252.py", line 23, in decode
UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f in position 29: character maps to <undefined>

With no reader draining the pipe, the OS pipe buffer fills and the worker blocks forever on its next stderr write — deadlocked inside the diffusion loop with no error surfaced. Any tqdm output triggers it, so on Windows this bites on essentially every generation and every in-worker weight download.

Fix

  • Decode both pipes explicitly as UTF-8 with errors="replace" so a stray byte can never kill the reader thread.
  • Set PYTHONIOENCODING=utf-8 for the child (via setdefault, so an explicit override is respected) so both ends of the pipe agree on the encoding.

Verification

Reproduced on Windows 11 with the Hunyuan3D 2 Mini extension (RTX 5060 Ti): before the fix, three consecutive runs wedged at "Generating 3D shape…" (one sat for a full hour). After the fix, live tqdm streams through (Diffusion Sampling: 3/50, 10.53it/s) and the same run completes in ~80 s. Full test suite passes (npm test: 23 py + 77 node).

🤖 Generated with Claude Code

lightningpixel and others added 3 commits August 21, 2026 11:05
Modly had no SECURITY.md and no private channel for vulnerability
reports, which left email as the only route for researchers. Private
vulnerability reporting is now enabled on the repository; this points
people at it and sets expectations around it.

The policy leads with a threat model and lets the scope follow from it,
so that an excluded report comes with the reason it was excluded. Two
assumptions are deliberate: workflow files are untrusted input because
sharing them is normal, and any web page the user has open is an
untrusted caller of the loopback API. The second is why the
network-exposure exclusion is narrowed to deliberate exposure only --
a page in the user's own browser needs none.

Every claim was checked against the code. The policy does not call the
installer signed (no platform signs it), says nothing about PyTorch
(we do not ship it), and does not excuse social engineering on the
strength of UI warnings that do not exist.
…curity

docs: add a security policy with a private reporting route
With bare text=True the host decodes worker stdout/stderr with the
locale codec (cp1252 on Windows), which lacks bytes like 0x8f from
tqdm's block glyphs. The stderr reader thread then dies with
UnicodeDecodeError, the pipe fills, and the worker blocks forever on
its next write - generations wedge silently mid-run (and HF weight
downloads stall the same way). Decode both pipes as UTF-8 with
errors=replace and set PYTHONIOENCODING=utf-8 for the child so both
ends of the pipe agree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lightningpixel

Copy link
Copy Markdown
Owner

Code review

Found 1 issue:

  1. This fix duplicates a more complete fix already merged to dev (PR fix(extensions): force UTF-8 stdio on Windows to stop UnicodeEncodeError crashes #272, commit a607358), targeting main instead

PR #272 ("fix(extensions): force UTF-8 stdio on Windows to stop UnicodeEncodeError crashes") already fixed this exact deadlock on dev 9 days ago, touching the same _build_env()/Popen() call in extension_process.py, but via PYTHONUTF8=1 instead of PYTHONIOENCODING=utf-8. It also added child-side reconfiguration (api/services/stdio_utf8.py, called from api/main.py/api/runner.py), an equivalent fix for the Electron process-spawn path (electron/main/process-runner.ts), and fixed the related unicode-arrow prints that originally triggered the crash (api/services/generators/base.py, api/services/generator_registry.py). None of that is on main.

Merging this PR as-is (especially now that its base is being switched to dev) will collide with dev's existing fix on the same lines, and even if merged into main alone, it fixes only one of several places on main still missing the UTF-8 stdio fix.

# Only set SSL_CERT_FILE if not already provided (preserves corporate/custom certs).
if "SSL_CERT_FILE" not in env:
try:
import certifi
env["SSL_CERT_FILE"] = certifi.where()
except ImportError:
pass
# Keep the child's stdio UTF-8 so it matches the UTF-8 decode on
# our side of the pipes (Windows would otherwise pick cp1252).

Compare with the existing fix on dev: https://github.com/lightningpixel/modly/blob/dev/api/services/extension_process.py

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@lightningpixel
lightningpixel changed the base branch from main to dev August 28, 2026 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants