prestartup_script.py: unguarded original_stdout.flush() / original_stderr.flush() in sync_write crashes every prompt on Windows with no attached console (OSError: [Errno 22] Invalid argument)
- Dominant language
- Python
- Stars
- 133k
- Forks
- 15.7k
- Avg merge
- 1d 6h
- Merged PRs (30d)
- 155
Description
On Windows, when ComfyUI is launched in a way that leaves sys.__stdout__ pointing at an invalid console handle (common with desktop shortcuts, some launchers, or detached-process setups), every prompt that causes any custom node to call print() crashes with:
OSError: [Errno 22] Invalid argument
The failure is deterministic: it happens the first time any node's code calls print() during execution, because PipeWorker.sync_write() calls original_stdout.flush() unconditionally, and that flush hits EINVAL on the broken console handle. Nothing is cached (the node crashed), so the next prompt re-executes the same code path and crashes identically. In a batch run I had 1,294 / 1,294 prompts fail this way — every single one with the same traceback.
The good news is the fix is trivial: a matching flush() method a few lines below in the same file already handles this correctly with a try/except (OSError, ValueError): pass. The two unguarded flush sites just need the same treatment.
Environment
OS: Windows 11 (ComfyUI desktop install)
ComfyUI: (user to fill in — from the server startup banner)
ComfyUI-Manager: (user to fill in — commit/version shown at startup)
Python: 3.11+ (bundled venv)
Launcher: ComfyUI desktop app shortcut (no attached console, or a console whose handle is rejected by Windows on flush)
Reproduction
Launch ComfyUI on Windows in a way where sys.__stdout__.flush() raises OSError: [Errno 22]. The easiest reliable way I've seen is the ComfyUI desktop install launched from its Start-menu/desktop shortcut.
Run any workflow whose graph includes a custom node that calls print() during execution (in my case, kijai's DownloadAndLoadFlorence2Model, which prints a status line inside loadmodel()).
Every prompt fails with the traceback below.
Full traceback
File "...\ComfyUI\execution.py", line 534, in execute
output_data, output_ui, has_subgraph, has_pending_tasks = await get_output_data(...)
File "...\ComfyUI\execution.py", line 334, in get_output_data
return_values = await _async_map_node_over_list(...)
File "...\ComfyUI\execution.py", line 308, in _async_map_node_over_list
await process_inputs(input_dict, i)
File "...\ComfyUI\execution.py", line 296, in process_inputs
result = f(**inputs)
File "...\custom_nodes\comfyui-florence2\nodes.py", line 188, in loadmodel
print(f"Florence2 using {attention} for attention")
File "...\site-packages\comfyui_manager\prestartup_script.py", line 320, in write
self.sync_write(message)
File "...\site-packages\comfyui_manager\prestartup_script.py", line 341, in sync_write
original_stdout.flush()
File "...\ComfyUI\app\logger.py", line 35, in flush
super().flush()
OSError: [Errno 22] Invalid argument
The Florence2 node is incidental — it's just the first node in the workflow that calls print(). Any custom node that prints would hit this.
Root cause
In prestartup_script.py, PipeWorker.sync_write flushes original_stdout / original_stderr without guarding the call:
python# prestartup_script.py, around lines 337-344 (current main)
if not file_only:
with std_log_lock:
if self.is_stdout:
write_stdout(message)
original_stdout.flush() # <-- raises OSError on broken Windows console
else:
write_stderr(message)
original_stderr.flush()
A sibling branch a few lines above (the tqdm-matching path) has the same issue:
python# same file, around lines 312-316
if '100%' in message:
self.sync_write(message)
else:
write_stderr(message)
original_stderr.flush() # <-- same unguarded flush
Meanwhile the flush() method immediately below sync_write already handles this correctly:
pythondef flush(self):
...
with std_log_lock:
try:
if self.is_stdout:
original_stdout.flush()
else:
original_stderr.flush()
except (OSError, ValueError):
pass
So the intent to tolerate flush failures is already established in this file — it's just not applied at the two sync_write-path sites.
Proposed fix
Apply the same try / except (OSError, ValueError): pass pattern to both unguarded sites. Diff:
diff@@ sync_write, around line 340
if not file_only:
with std_log_lock:
- if self.is_stdout:
- write_stdout(message)
- original_stdout.flush()
- else:
- write_stderr(message)
- original_stderr.flush()
+ try:
+ if self.is_stdout:
+ write_stdout(message)
+ original_stdout.flush()
+ else:
+ write_stderr(message)
+ original_stderr.flush()
+ except (OSError, ValueError):
+ pass
@@ tqdm branch, around line 315
if '100%' in message:
self.sync_write(message)
else:
- write_stderr(message)
- original_stderr.flush()
+ try:
+ write_stderr(message)
+ original_stderr.flush()
+ except (OSError, ValueError):
+ pass
Impact of the fix
A failed flush on a broken console handle just means the line isn't force-drained to that console — content still reaches log_file (which has its own independently guarded flush a few lines up) and the in-memory log/stdout wrappers. No data loss, no behavioral change on healthy stdout, and 100% of these prompt-crash-loops go away on affected Windows installs.
I applied this patch locally and Florence2 (and the rest of my batch, 1,294 prompts) started running correctly immediately.
Happy to open a PR if preferred — just wanted to surface the root cause and the diff for a maintainer to take whichever shape is easiest.
Contributor guide
Research direction
Open prestartup_script.py and inspect PipeWorker.sync_write around the two original_stdout/original_stderr.flush() calls, comparing them with the guarded flush() method below. Reproduce with a Windows detached-console launch and a printing custom node, then verify both flush sites tolerate OSError and ValueError without crashing prompts.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100