From 14cf2d85cafec7a92184313c4490fb3200ab0af5 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:15:15 -0700 Subject: [PATCH] fix(display): guard isatty() against closed streams via _is_tty property (#3056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In gateway/Telegram mode, the stdout fd can be closed by executor thread cleanup. KawaiiSpinner.stop() called isatty() on the closed fd, raising ValueError and masking the original error. Instead of a point fix, add a _is_tty property that centralizes the closed-stream guard — both _animate() and stop() now use it. Follows the same (ValueError, OSError) pattern already in _write(). Inspired by PR #2632 by bot-deo88. --- agent/display.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/agent/display.py b/agent/display.py index 462d2a439..b574c485e 100644 --- a/agent/display.py +++ b/agent/display.py @@ -252,6 +252,14 @@ class KawaiiSpinner: except (ValueError, OSError): pass + @property + def _is_tty(self) -> bool: + """Check if output is a real terminal, safe against closed streams.""" + try: + return hasattr(self._out, 'isatty') and self._out.isatty() + except (ValueError, OSError): + return False + def _is_patch_stdout_proxy(self) -> bool: """Return True when stdout is prompt_toolkit's StdoutProxy. @@ -272,7 +280,7 @@ class KawaiiSpinner: # When stdout is not a real terminal (e.g. Docker, systemd, pipe), # skip the animation entirely — it creates massive log bloat. # Just log the start once and let stop() log the completion. - if not hasattr(self._out, 'isatty') or not self._out.isatty(): + if not self._is_tty: self._write(f" [tool] {self.message}", flush=True) while self.running: time.sleep(0.5) @@ -343,7 +351,7 @@ class KawaiiSpinner: if self.thread: self.thread.join(timeout=0.5) - is_tty = hasattr(self._out, 'isatty') and self._out.isatty() + is_tty = self._is_tty if is_tty: # Clear the spinner line with spaces instead of \033[K to avoid # garbled escape codes when prompt_toolkit's patch_stdout is active.