fix(io): catch ValueError in _SafeWriter for closed file handles (#2428)
When subagents run in ThreadPoolExecutor threads, the shared stdout handle can close between thread teardown and KawaiiSpinner cleanup. Python raises ValueError (not OSError) for I/O operations on closed files: ValueError: I/O operation on closed file The _SafeWriter class was only catching OSError, missing this case. Changes: - Add ValueError to exception handling in write(), flush(), and isatty() - Update docstring to document the ThreadPoolExecutor teardown scenario Fixes #2428
This commit is contained in:
15
run_agent.py
15
run_agent.py
@@ -108,7 +108,7 @@ HONCHO_TOOL_NAMES = {
|
||||
|
||||
|
||||
class _SafeWriter:
|
||||
"""Transparent stdio wrapper that catches OSError from broken pipes.
|
||||
"""Transparent stdio wrapper that catches OSError/ValueError from broken pipes.
|
||||
|
||||
When hermes-agent runs as a systemd service, Docker container, or headless
|
||||
daemon, the stdout/stderr pipe can become unavailable (idle timeout, buffer
|
||||
@@ -117,8 +117,13 @@ class _SafeWriter:
|
||||
run_conversation() — especially via double-fault when an except handler
|
||||
also tries to print.
|
||||
|
||||
Additionally, when subagents run in ThreadPoolExecutor threads, the shared
|
||||
stdout handle can close between thread teardown and cleanup, raising
|
||||
``ValueError: I/O operation on closed file`` instead of OSError.
|
||||
|
||||
This wrapper delegates all writes to the underlying stream and silently
|
||||
catches OSError. It is transparent when the wrapped stream is healthy.
|
||||
catches both OSError and ValueError. It is transparent when the wrapped
|
||||
stream is healthy.
|
||||
"""
|
||||
|
||||
__slots__ = ("_inner",)
|
||||
@@ -129,13 +134,13 @@ class _SafeWriter:
|
||||
def write(self, data):
|
||||
try:
|
||||
return self._inner.write(data)
|
||||
except OSError:
|
||||
except (OSError, ValueError):
|
||||
return len(data) if isinstance(data, str) else 0
|
||||
|
||||
def flush(self):
|
||||
try:
|
||||
self._inner.flush()
|
||||
except OSError:
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
|
||||
def fileno(self):
|
||||
@@ -144,7 +149,7 @@ class _SafeWriter:
|
||||
def isatty(self):
|
||||
try:
|
||||
return self._inner.isatty()
|
||||
except OSError:
|
||||
except (OSError, ValueError):
|
||||
return False
|
||||
|
||||
def __getattr__(self, name):
|
||||
|
||||
Reference in New Issue
Block a user