fix(code_execution): recover from closed PTY sessions
Detect closed or exited local TTY sessions before writing, convert invalid PTY write errors into retryable session failures, and reset/retry the terminal session once after send/read failures.
Agent Zero Local committed
May 2, 2026 at 13:34 UTC
a0f0c2e8d2ca12f194fe50c553eda8b2824084fa
2 files changed
+16
-7
plugins/_code_execution/helpers/tty_session.py
+12
-2
@@ -97,10 +97,20 @@ class TTYSession:
97
async def send(self, data: str | bytes):
98
if self._proc is None:
99
raise RuntimeError("TTYSpawn is not started")
100
+ if self._pty_master is None and not _IS_WIN:
101
+ raise RuntimeError("TTYSpawn PTY is closed")
102
+ if getattr(self._proc, "returncode", None) is not None:
103
+ raise RuntimeError("TTYSpawn process has exited")
104
if isinstance(data, str):
105
data = data.encode(self.encoding)
102
- self._proc.stdin.write(data) # type: ignore
103
- await self._proc.stdin.drain() # type: ignore
106
+ try:
107
+ self._proc.stdin.write(data) # type: ignore
108
+ await self._proc.stdin.drain() # type: ignore
109
+ except OSError as e:
110
+ if e.errno in (errno.EBADF, errno.EIO, errno.EINVAL):
111
+ self._pty_master = None
112
+ raise RuntimeError("TTYSpawn PTY is closed") from e
113
+ raise
114
115
async def sendline(self, line: str):
116
await self.send(line + "\n")
plugins/_code_execution/tools/code_execution_tool.py
+4
-5
@@ -194,12 +194,11 @@ class CodeExecution(Tool):
194
)
195
196
except Exception as e:
197
- if i == 1:
198
- PrintStyle.error(str(e))
199
- await self.prepare_state(cfg, reset=True, session=session)
197
+ PrintStyle.error(str(e))
198
+ await self.prepare_state(cfg, reset=True, session=session)
199
+ if i == 0:
200
continue
201
- else:
202
- raise e
201
+ raise e
202
203
def format_command_for_output(self, command: str):
204
short_cmd = command[:250]