Comfy-Org / Comfy-Org/ComfyUI-Manager
ComfyUIManagerLogger breaks colorama: isatty() hardcoded to False and missing closed attribute strip ANSI colors from print() on Windows
- Dominant language
- Python
- Stars
- 16.1k
- Forks
- 2.5k
- Avg merge
- 5d 4h
- Merged PRs (30d)
- 13
Description
# ANSI colors stripped from print() when file logging is on — ComfyUIManagerLogger reports isatty()==False
**Repo:** Comfy-Org/ComfyUI-Manager (formerly ltdrdata/ComfyUI-Manager; old address redirects)
**Version:** 3.41
**OS:** Windows 11 (any Windows where colorama is installed)
## Symptom
With `enable_file_logging = True`, colored `print()` output loses its ANSI codes **from a certain point onward** — in both the console and the `.log` file. By contrast, messages emitted through ComfyUI's own logger (i.e. `logging.info/warning/...` via the `logging` module, as ComfyUI core and most nodes do) keep their colors throughout.
The break point is *not* the start of startup: everything before a specific node's import is colored, everything after is not. That's what makes it look "intermittent" but it's fully deterministic.
## Trigger
Any package that calls `colorama.init()` at import time. The one hitting this today is **deepdiff**, but the real condition is not a version number — it's the **presence of `deepdiff/colored_view.py`**:
- `deepdiff/colored_view.py` runs `colorama.init()` on Windows at import time (when colorama is installed), guarded only by `os.name == "nt" and find_spec("colorama")`.
- `deepdiff/diff.py` imports that module at package import, so it fires the moment any node imports `deepdiff`.
The trigger is therefore "the installed deepdiff ships a `colored_view.py` that calls `colorama.init()`". Which versions have that file varies — it is **not** simply "≥ 9.0". Empirically, on the affected install:
- deepdiff **8.6.2** → still has `colored_view.py` → bug **present**.
- deepdiff **8.5.0** → no `colored_view.py` → bug **gone**.
(The exact version boundary is a deepdiff packaging detail and changes between releases, so it's not worth pinning a "below X" recommendation here.)
deepdiff is **not** part of the base ComfyUI portable package — a fresh download ships with no deepdiff at all. It only appears because a node installs it: **ComfyUI-Crystools** declares `deepdiff` **unpinned** (`requirements.txt` is just `deepdiff`), so on install it pulls whatever the latest release is at that time. On a machine whose install predates the deepdiff release that added `colored_view.py`, the bug never fires — same node, same OS, different outcome, purely by install date.
So the color strip begins exactly at Crystools' import (where `deepdiff` is first loaded) and affects every `print()` after it.
## Root cause
`ComfyUIManagerLogger` (prestartup_script.py) replaces `sys.stdout`/`sys.stderr` when file logging is on. As of `main` (verified 2026-09-09, commit `f82970b7`, release 3.41 — the line numbers below are stable in current `main`):
- `def isatty(self):` at line 302 hardcodes `return False` at line 303, and
- the class (line 286) defines **no `closed` attribute** anywhere in the file.
`colorama`'s `AnsiToWin32.__init__` probes the stream:
```python
have_tty = not self.stream.closed and self.stream.isatty()
...
if strip is None:
strip = need_conversion or not have_tty
```
With `closed` missing (→ colorama's `StreamWrapper.closed` property catches `AttributeError` and returns `True`) and `isatty()` hardcoded `False`, `have_tty` is `False` → `strip` is forced `True` unconditionally → colorama wraps `sys.stdout` in a stripping `StreamWrapper`.
Every later `print()` resolves `sys.stdout` **by name** → hits the wrapper → ANSI bytes are deleted before the write → color gone in both the console and the file log. `logging.*` keeps color only because its `StreamHandler` holds the **pre-wrap stream object by reference** and bypasses the wrapper.
Note the class already does the *correct* thing for `fileno()` — it delegates to `original_stdout`/`original_stderr`. The hardcoded `isatty()` and missing `closed` are the inconsistent part.
## Proposed fix
Replace the hardcoded `isatty()` and add the missing `closed` property, in `ComfyUIManagerLogger`, delegating to the real underlying streams the class already captures (it already does this for `fileno()` directly above):
```diff
--- prestartup_script.py
+++ prestartup_script.py
@@ -300,7 +300,27 @@
raise ValueError("The object does not have a fileno method")
def isatty(self):
- return False
+ # Delegate to the real console we wrap, not a hardcoded False.
+ # Returning False here made colorama (and any ANSI-aware lib) believe
+ # the stream is a pipe and strip every ANSI code from the print() path
+ # in both the console and the file log. The console still receives the
+ # text unchanged -- this only tells colorama the truth about the target.
+ stream = original_stdout if self.is_stdout else original_stderr
+ try:
+ return stream.isatty()
+ except (AttributeError, ValueError, OSError):
+ return False
+
+ @property
+ def closed(self):
+ # Same reasoning as isatty(): without this attribute, colorama's
+ # StreamWrapper.closed falls back to True (AttributeError -> closed),
+ # which again forces strip=True. Report the real underlying state.
+ stream = original_stdout if self.is_stdout else original_stderr
+ try:
+ return stream.closed
+ except (AttributeError, ValueError):
+ return True
def write(self, message):
global is_start_mode
```
With honest answers, in a real terminal colorama's `should_wrap()` is `False`, so it never wraps and `print()` keeps its ANSI in both the console and the log. When output is genuinely redirected to a pipe, colorama strips correctly (which is the desired behavior there).
## Why it's a Manager bug, not a deepdiff bug
deepdiff's import-time `colorama.init()` is only the **first trigger**. The defect is the logger lying about the stream; any ANSI-aware library that consults `isatty()`/`closed` before emitting color would hit the same strip. Downgrading deepdiff or pinning Crystools only papers over the symptom.
## Verified
Patch applied locally to Manager 3.41 on a Windows 11 / RTX 5090+4060Ti install; all post-Crystools `print()` lines are colored again in both the console and the fresh `user/comfyui_*.log`.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in prestartup_script.py at ComfyUIManagerLogger, especially its fileno() and isatty() methods and stream-capturing setup. Reproduce with file logging enabled on Windows and an import-time colorama.init() trigger such as deepdiff. Done means print() retains ANSI colors in the console and log when attached to a terminal, while redirected output still strips them appropriately.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100